From 055b79f450129a6f2bbb0707517ac091e93e245a Mon Sep 17 00:00:00 2001 From: Zhuinden Date: Sat, 14 Jan 2017 00:30:25 +0100 Subject: [PATCH] share backstack via Activity.getSystemService(), share key via ContextWrapper.getSystemService() --- CHANGELOG.md | 17 +++++++ README.md | 39 ++++++++-------- .../simplestackdemoexample/MainActivity.java | 30 ++++++++----- .../demo/BackstackHolder.java | 11 ----- .../demo/FirstView.java | 28 +++++++----- .../demo/KeyContextWrapper.java | 45 +++++++++++++++++++ .../demo/KeyHolder.java | 9 ---- .../demo/SecondView.java | 27 ++++++----- 8 files changed, 134 insertions(+), 72 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/BackstackHolder.java create mode 100644 demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/KeyContextWrapper.java delete mode 100644 demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/KeyHolder.java diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..d9f77ae4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Change log + +Simple Stack 0.1.1 (2017-01-14) +--------------------------------- +- Key and backstack are now provided to custom viewgroup via `getSystemService()` + +Simple Stack 0.1.0 (2017-01-13) +--------------------------------- +- Added initial `Backstack`, `StateChange` and `StateChanger` classes. +- Backstack allows manipulation of state via `goTo()`, `goBack()` and `setHistory()`. +- Demo persists backstack history through config change and process death. + +Limitations: +- ViewState is not persisted +- scheduling state changes (starting a state change while another is in progress) is not allowed +- there is a possibility that state change can occur even after `onPause()` +- key and backstack are manually set to the custom viewgroup, which means these are not directly accessible in their child views (and the interfaces are ugly anyways) \ No newline at end of file diff --git a/README.md b/README.md index da714d88..ed5ae84b 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ Afterwards, the backstack operators allow changing between states. ``` java public class MainActivity extends AppCompatActivity implements StateChanger { + public static final String BACKSTACK = "BACKSTACK"; + @BindView(R.id.root) RelativeLayout root; @@ -48,31 +50,29 @@ public class MainActivity extends AppCompatActivity implements StateChanger { setContentView(R.layout.activity_main); ButterKnife.bind(this); - // initialize backstack from persisted state if exists, otherwise with new elements. ArrayList keys; if(savedInstanceState != null) { - keys = savedInstanceState.getParcelableArrayList("BACKSTACK"); + keys = savedInstanceState.getParcelableArrayList(BACKSTACK); } else { keys = new ArrayList<>(); keys.add(new FirstKey()); + } backstack = (Backstack)getLastCustomNonConfigurationInstance(); if(backstack == null) { backstack = new Backstack(keys); } - - // set this as state changer, and handle initialization backstack.setStateChanger(this); } @Override - public Object onRetainCustomNonConfigurationInstance() { // persist stack across config change + public Object onRetainCustomNonConfigurationInstance() { return backstack; } @Override public void onBackPressed() { - if(!backstack.goBack()) { // handle back press + if(!backstack.goBack()) { super.onBackPressed(); } } @@ -82,12 +82,12 @@ public class MainActivity extends AppCompatActivity implements StateChanger { super.onSaveInstanceState(outState); ArrayList history = new ArrayList<>(); history.addAll(backstack.getHistory()); - outState.putParcelableArrayList("BACKSTACK", history); // persist state of backstack across config change/process death + outState.putParcelableArrayList(BACKSTACK, history); } @Override protected void onDestroy() { - backstack.removeStateChanger(); // backstack survives config change, so Activity should remove its reference + backstack.removeStateChanger(); super.onDestroy(); } @@ -98,16 +98,21 @@ public class MainActivity extends AppCompatActivity implements StateChanger { completionCallback.stateChangeComplete(); return; } - root.removeAllViews(); // no state persistence for previous views yet + root.removeAllViews(); Key newKey = stateChange.topNewState(); - View view = LayoutInflater.from(this).inflate(newKey.layout(), root, false); - BackstackHolder backstackHolder = (BackstackHolder)view; - backstackHolder.setBackstack(backstack); // view currently doesn't implicitly receive the backstack - KeyHolder keyHolder = (KeyHolder)view; - keyHolder.setKey(newKey); // view currently doesn't implicitly receive the key + Context newContext = new KeyContextWrapper(this, newKey); + View view = LayoutInflater.from(newContext).inflate(newKey.layout(), root, false); root.addView(view); completionCallback.stateChangeComplete(); } + + @Override + public Object getSystemService(String name) { + if(BACKSTACK.equals(name)) { + return backstack; + } + return super.getSystemService(name); + } } ``` @@ -117,8 +122,4 @@ Currently, the backstack does **not** do viewstate persistence, only stores the Scheduling a state change while a state change is already in progress throws an `IllegalStateException` instead of queueing it. -## Demo limitations - -In the demo, keys associated with the given custom views are manually set for the custom views, therefore it is set via an interface, but is not accessible for any child views of the custom viewgroup. - -In the demo, the backstack is also manually set to the custom viewgroup using a `BackStackHolder` interface, instead of implicitly providing it via the Context. +It is possible to start a state change even after `onPause()` instead of queueing it. \ No newline at end of file diff --git a/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/MainActivity.java b/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/MainActivity.java index 97c2b355..8081eb77 100644 --- a/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/MainActivity.java +++ b/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/MainActivity.java @@ -1,25 +1,28 @@ package com.zhuinden.simplestackdemoexample; +import android.content.Context; +import android.os.Bundle; import android.os.Parcelable; import android.support.v7.app.AppCompatActivity; -import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.widget.RelativeLayout; + import com.zhuinden.simplestackdemo.stack.Backstack; import com.zhuinden.simplestackdemo.stack.StateChange; import com.zhuinden.simplestackdemo.stack.StateChanger; -import com.zhuinden.simplestackdemoexample.demo.BackstackHolder; import com.zhuinden.simplestackdemoexample.demo.FirstKey; import com.zhuinden.simplestackdemoexample.demo.Key; -import com.zhuinden.simplestackdemoexample.R; -import com.zhuinden.simplestackdemoexample.demo.KeyHolder; +import com.zhuinden.simplestackdemoexample.demo.KeyContextWrapper; import java.util.ArrayList; + import butterknife.BindView; import butterknife.ButterKnife; public class MainActivity extends AppCompatActivity implements StateChanger { + public static final String BACKSTACK = "BACKSTACK"; + @BindView(R.id.root) RelativeLayout root; @@ -33,7 +36,7 @@ protected void onCreate(Bundle savedInstanceState) { ArrayList keys; if(savedInstanceState != null) { - keys = savedInstanceState.getParcelableArrayList("BACKSTACK"); + keys = savedInstanceState.getParcelableArrayList(BACKSTACK); } else { keys = new ArrayList<>(); keys.add(new FirstKey()); @@ -62,7 +65,7 @@ protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); ArrayList history = new ArrayList<>(); history.addAll(backstack.getHistory()); - outState.putParcelableArrayList("BACKSTACK", history); + outState.putParcelableArrayList(BACKSTACK, history); } @Override @@ -80,12 +83,17 @@ public void handleStateChange(StateChange stateChange, Callback completionCallba } root.removeAllViews(); Key newKey = stateChange.topNewState(); - View view = LayoutInflater.from(this).inflate(newKey.layout(), root, false); - BackstackHolder backstackHolder = (BackstackHolder)view; - backstackHolder.setBackstack(backstack); - KeyHolder keyHolder = (KeyHolder)view; - keyHolder.setKey(newKey); + Context newContext = new KeyContextWrapper(this, newKey); + View view = LayoutInflater.from(newContext).inflate(newKey.layout(), root, false); root.addView(view); completionCallback.stateChangeComplete(); } + + @Override + public Object getSystemService(String name) { + if(BACKSTACK.equals(name)) { + return backstack; + } + return super.getSystemService(name); + } } diff --git a/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/BackstackHolder.java b/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/BackstackHolder.java deleted file mode 100644 index 1b16bfd9..00000000 --- a/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/BackstackHolder.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.zhuinden.simplestackdemoexample.demo; - - -import com.zhuinden.simplestackdemo.stack.Backstack; -/** - * Created by Owner on 2017. 01. 12.. - */ - -public interface BackstackHolder { - void setBackstack(Backstack backstack); -} diff --git a/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/FirstView.java b/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/FirstView.java index bf7c4205..17f48e9b 100644 --- a/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/FirstView.java +++ b/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/FirstView.java @@ -5,36 +5,52 @@ import android.util.AttributeSet; import android.view.View; import android.widget.RelativeLayout; + import com.zhuinden.simplestackdemo.stack.Backstack; +import com.zhuinden.simplestackdemoexample.MainActivity; import com.zhuinden.simplestackdemoexample.R; + import butterknife.ButterKnife; import butterknife.OnClick; /** * Created by Owner on 2017. 01. 12.. */ -public class FirstView extends RelativeLayout implements BackstackHolder, KeyHolder { +public class FirstView + extends RelativeLayout { public FirstView(Context context) { super(context); + init(context); } public FirstView(Context context, AttributeSet attrs) { super(context, attrs); + init(context); } public FirstView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + init(context); } @TargetApi(21) public FirstView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + init(context); } Backstack backstack; FirstKey firstKey; + private void init(Context context) { + if(!isInEditMode()) { + // noinspection ResourceType + backstack = (Backstack) context.getSystemService(MainActivity.BACKSTACK); + firstKey = KeyContextWrapper.getKey(context); + } + } + @OnClick(R.id.first_button) public void clickButton(View view) { backstack.goTo(new SecondKey()); @@ -45,14 +61,4 @@ protected void onFinishInflate() { super.onFinishInflate(); ButterKnife.bind(this); } - - @Override - public void setBackstack(Backstack backstack) { - this.backstack = backstack; - } - - @Override - public void setKey(Key key) { - this.firstKey = (FirstKey)key; - } } diff --git a/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/KeyContextWrapper.java b/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/KeyContextWrapper.java new file mode 100644 index 00000000..de3eb077 --- /dev/null +++ b/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/KeyContextWrapper.java @@ -0,0 +1,45 @@ +package com.zhuinden.simplestackdemoexample.demo; + +import android.content.Context; +import android.content.ContextWrapper; +import android.os.Parcelable; +import android.view.LayoutInflater; + +/** + * Created by Zhuinden on 2017.01.14.. + */ + +public class KeyContextWrapper + extends ContextWrapper { + public static final String KEY = "KEY"; + + LayoutInflater layoutInflater; + + final Parcelable key; + + public KeyContextWrapper(Context base, Parcelable key) { + super(base); + this.key = key; + } + + @Override + public Object getSystemService(String name) { + if(Context.LAYOUT_INFLATER_SERVICE.equals(name)) { + if(layoutInflater == null) { + layoutInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); + } + return layoutInflater; + } else if(KEY.equals(name)) { + return key; + } + return super.getSystemService(name); + } + + + public static T getKey(Context context) { + // noinspection ResourceType + Object key = context.getSystemService(KEY); + // noinspection unchecked + return (T) key; + } +} diff --git a/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/KeyHolder.java b/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/KeyHolder.java deleted file mode 100644 index b680ec4e..00000000 --- a/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/KeyHolder.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.zhuinden.simplestackdemoexample.demo; - -/** - * Created by Owner on 2017. 01. 13.. - */ - -public interface KeyHolder { - void setKey(Key key); -} diff --git a/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/SecondView.java b/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/SecondView.java index da6f3f92..d85698ac 100644 --- a/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/SecondView.java +++ b/demo-stack-example/src/main/java/com/zhuinden/simplestackdemoexample/demo/SecondView.java @@ -4,41 +4,46 @@ import android.content.Context; import android.util.AttributeSet; import android.widget.RelativeLayout; + import com.zhuinden.simplestackdemo.stack.Backstack; +import com.zhuinden.simplestackdemoexample.MainActivity; /** * Created by Owner on 2017. 01. 12.. */ -public class SecondView extends RelativeLayout implements BackstackHolder, KeyHolder { +public class SecondView + extends RelativeLayout { public SecondView(Context context) { super(context); + init(context); } public SecondView(Context context, AttributeSet attrs) { super(context, attrs); + init(context); } public SecondView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + init(context); } @TargetApi(21) public SecondView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + init(context); + } + + private void init(Context context) { + if(!isInEditMode()) { + // noinspection ResourceType + backstack = (Backstack) context.getSystemService(MainActivity.BACKSTACK); + secondKey = KeyContextWrapper.getKey(context); + } } Backstack backstack; SecondKey secondKey; - - @Override - public void setBackstack(Backstack backstack) { - this.backstack = backstack; - } - - @Override - public void setKey(Key key) { - this.secondKey = (SecondKey)key; - } }