SlideShare a Scribd company logo
1 of 42
Architecting Single-Activity
Applications
With or without Fragments
Gabor Varadi
@zhuinden
What do we think we know about
Activities?
From https://developer.android.com/guide/components/intents-filters.html
„Starting an activity
An Activity represents a single screen in an app.
You can start a new instance of an Activity by
passing an Intent to startActivity().
The Intent describes the activity to start and
carries any necessary data.”
What do we think we know about
Fragments?
From https://developer.android.com/guide/components/fragments.html
„A Fragment represents a behavior or a portion
of user interface in an Activity. You can combine
multiple fragments in a single activity to build a
multi-pane UI and reuse a fragment in multiple
activities.”
What is the truth?
Dianne Hackborn
„How should I design my Android
application?”
Activity
Once we have gotten in to this entry-point to your UI, we really don't
care how you organize the flow inside.
Make it all one activity with manual changes to its views, use
fragments (a convenience framework we provide) or some other
framework, or split it into additional internal activities. Or do all three
as needed.
As long as you are following the high-level contract of activity (it
launches in the proper state, and saves/restores in the current state),
it doesn't matter to the system.
What does that tell us?
• Activities are not „screens”, they are entry points to the
app (like a main function)
• The high-level Activity contract is showing UI for current
state, handling initial state, and persist state across
configuration change and process death
• Fragments are just a „convenience framework” —
technically they are ViewControllers with lifecycle
integration
• Android does NOT care how you handle the flow inside
your application!
Wait, process death?
• Step 1: put app in background with HOME
• Step 2: press „Terminate application”
• Step 3: restart app from launcher
• Step 4: enjoy strange behavior 
(app restart, statics are cleared, savedInstanceState != null)
What IS the flow inside your
application?
• Navigation
– where you are in your application (and what to show)
– where you came from, back/up navigation
– remembering navigation state across config change
and process death
• Scoping
– what data needs to be shown
– what services need to exist (singleton and subscopes)
– how to keep scoped services alive across config
change
Some magic tricks
(that we need to understand first)
Passing objects through the context
hierarchy: getSystemService() trick
• Any object can be exposed via the Context
hierarchy by overriding getSystemService()
• Objects from Activity (and the activity!) can be
exposed directly via Activity.getSystemService()
• Objects in subscope of Activity can be exposed
through ContextWrapper.getSystemService()
by inflating the view with a cloned layout inflater
LayoutInflater.from(baseContext)
.cloneInContext(contextWrapper);
Exposing objects through the Context
from the Activity
public class MainActivity
extends AppCompatActivity {
private static final String TAG = "MainActivity";
public static MainActivity get(Context context) {
// noinspection ResourceType
return (MainActivity)context.getSystemService(TAG);
}
@Override
public Object getSystemService(String name) {
if(TAG.equals(name)) {
return this; // <-- now MainActivity.get(context) works
}
return super.getSystemService(name);
}
}
public class KeyContextWrapper extends ContextWrapper {
public static final String TAG = "Backstack.KEY";
LayoutInflater layoutInflater;
final Object key;
public KeyContextWrapper(Context base, @NonNull Object key) {
super(base);
this.key = key;
}
public static <T> T getKey(Context context) {
// noinspection ResourceType
Object key = context.getSystemService(TAG);
// noinspection unchecked
return (T) 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(TAG.equals(name)) {
return key; // <-- now KeyContextWrapper.getKey(context) works
}
return super.getSystemService(name);
}
}
Back to application flows!
Scoping
Allowing data and services to exist for the entire duration of
when the screen is visible, and not be killed on configuration
changes.
Child scopes should be able to inherit from their superscope.
Things that set out to solve scoping problem:
- Activity: onRetainCustomNonConfigurationInstance()
- Fragment: retained fragments
- Loaders
- square/Mortar: MortarScope
- lyft/Scoop: Scoop
- zhuinden/Service-Tree: ServiceTree
- Architectural Components: ViewModel
Goal of scoping
• The goal is to make sure the data and services exist for as long as
the scope
• When the scope is destroyed (as it is no longer needed), the data
and services are torn down along with it
• In advanced use:
– scoped data becomes a dependency that is provided to
constructor, but obtained asynchronously and observed for
changes
(LiveData, BehaviorRelay, Observable + RxReplayingShare)
– Dagger component is subscoped, and provides the data as
scoped dependency
– The Dagger component is stored in the scope to survive
configuration changes
@Subscope
@dagger.Component(
dependencies = {SingletonComponent.class},
modules = {ChatModule.class})
)
interface ChatComponent {
ChatPresenter chatPresenter();
void inject(ChatView chatView);
}
@dagger.Module
static class ChatModule {
private final int chatId;
@Provides
@Subscope
Observable<Chat> chat(ChatRepository chatRepository) {
return chatRepository.getChat(chatId);
}
}
@Subscope
static class ChatPresenter implements Presenter<ChatView> {
@Inject
ChatPresenter(Observable<Chat> chat) {
// ...
}
}
String scopeTag = chatKey.toString();
MortarScope childScope = parentScope.findChild(scopeTag);
if (childScope == null) {
childScope = parentScope.buildChild()
.withService(DaggerService.SERVICE_NAME,
key.createComponent(parentScope))
.build(scopeTag);
}
return childScope;
----------------------------------------------------
ChatComponent component =
DaggerService.<ChatComponent>getService(context);
----------------------------------------------------
MortarScope.getScope(context).destroy();
Creating/Destroying Scopes: Mortar
The common approach to simplifying
the problem
• Create only Singleton scope (and a single global
injector), everything else is unscoped
• Unscoped dependencies have their state
persisted to Bundle, and restored if state exists
• Also: if the ViewController is preserved even
without its view hierarchy, then it can BE the
scope!
(retained fragments, Conductor’s Controller)
Navigation
• We must know where we are, remember where we have been
• This state must be preserved across configuration changes and
process death
• Things that set out to solve Navigation problem:
– Activity record stack
– Fragment backstack
– square/flow 0.8
– lyft/scoop
– square/flow 1.0-alpha3
– terrakok/Cicerone (no backstack, only command queue)
– bluelinelabs/Conductor
– zhuinden/simple-stack
– wealthfront/magellan (don’t use it – does NOT preserve state
across process death!!!)
Checklist for what a backstack should
be able to do
• Handling state persistence across config change /
process death
• Should receive both the previous and the new state on
state change
• Animations are asynchronous – operations must be
enqueued
• State changer is not always available (after onPause) –
operations must be enqueued
How do Activities handle navigation?
• Intents to start new Activities
• Parameters are provided in the extras Bundle, generally
as a dynamically typed storage with string keys
• Intents have „intent flags” to manipulate „task stack”
(CLEAR_TOP, REORDER_TO_FRONT, etc.)
• Downsides:
– You can’t easily tell what Activities exist in the background
– Modifying stack needs tricky combinations of intent flags
(no fine-grained control)
– No notifications about change (previous state, new state)
– Complicated lifecycle if multiple Activities exist
--- APP START
D/MainActivity: onCreate
D/MainActivity: onStart
D/MainActivity: onResume
D/MainActivity: onPostResume
--- START SECOND ACTIVITY in onPostResume()
D/MainActivity: onPause
D/SecondActivity: onCreate
D/SecondActivity: onStart
D/SecondActivity: onResume
D/SecondActivity: onPostResume
D/MainActivity: onSaveInstanceState
D/MainActivity: onStop
--- FINISH SECOND ACTIVITY
D/SecondActivity: onPause
D/MainActivity: onStart
D/MainActivity: onResume
D/MainActivity: onPostResume
D/SecondActivity: onStop
D/SecondActivity: onDestroy
How are Fragments generally used for
navigation?
• FragmentTransactions (begin/commit)
• Tutorials typically show „replace()” in conjunction with
„addToBackStack()”
• The backstack stores transactions (operations) with a tag to
pop to (inclusive/exclusive)
• (Parameters are also provided as Bundle, called arguments)
• Downsides:
– onBackstackChanged() provides change notification, but does
not provide previous and new states (also it’s kinda random)
– Stack stores operations instead of active fragments, so
asymmetric navigation is super-difficult
– commit() runs transaction on the NEXT event loop
(what about onPause?
„Cannot perform after onSaveInstanceState()”)
Principle of Flow (and its variants)
• „Flow” is a custom backstack to store current state and
history
• Content of the backstack is saved to and restored from
Bundle (for process death) as Parcelables
• State is represented as immutable parcelable value objects,
called Keys
• Keys contain all necessary data in order to set up the initial
state (like Intent extras), but as typed values of the class
• List of previous / new keys are both available („Traversal”,…)
@AutoValue
public abstract class TaskDetailKey
implements Key, Parcelable {
public abstract String taskId(); // <- instead of static final TASK_ID = „TASK_ID”;
public static TaskDetailKey create(String taskId) {
return new AutoValue_TaskDetailKey(R.layout.path_taskdetail, taskId);
}
@Override
public int menu() {
return R.menu.taskdetail_fragment_menu;
}
@Override
public boolean isFabVisible() {
return true;
}
@Override
public View.OnClickListener fabClickListener(View view) {
return v -> {
((TaskDetailView)view).editTask();
};
}
@Override
public int fabDrawableIcon() {
return R.drawable.ic_edit;
}
}
@PaperParcel
data class TaskDetailKey(val taskId: String)
: Key, PaperParcelable {
override fun layout() = R.layout.task_detail
override fun menu() = R.menu.taskdetail_fragment_menu
override fun isFabVisible() = true
override fun fabClickListener(view: View) {
return View.OnClickListener {
v -> (view as SecondView).editTask()
}
}
override fun fabDrawableIcon() = R.drawable.ic_edit
companion object {
@JvmField val CREATOR = PaperParcelTaskDetailKey.CREATOR
}
}
public void setupViewsForKey(Key key, View newView) {
if(key.shouldShowUp()) {
setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED, GravityCompat.START);
MainActivity.get(getContext()).getSupportActionBar()
.setDisplayHomeAsUpEnabled(true);
drawerToggle.setDrawerIndicatorEnabled(false);
} else {
setDrawerLockMode(LOCK_MODE_UNLOCKED, GravityCompat.START);
MainActivity.get(getContext()).getSupportActionBar()
.setDisplayHomeAsUpEnabled(false);
drawerToggle.setDrawerIndicatorEnabled(true);
}
drawerToggle.syncState();
setCheckedItem(key.navigationViewId());
MainActivity.get(getContext()).supportInvalidateOptionsMenu();
if(key.isFabVisible()) {
fabAddTask.setVisibility(View.VISIBLE);
} else {
fabAddTask.setVisibility(View.GONE);
}
fabAddTask.setOnClickListener(key.fabClickListener(newView));
if(key.fabDrawableIcon() != 0) {
fabAddTask.setImageResource(key.fabDrawableIcon());
}
}
Example: set up view by key
Displaying the View for a given Key
• Our Key specifies what we want to show
• We need to handle the events of the views (clicks, text
changes, etc.)
• For that, we need a „ViewController” (which can be used
inside an Activity, so not an Activity)
• Possible options:
– Custom ViewGroup
– Fragment
– lyft/scoop’s ViewController
– square/coordinators’s Coordinator
– bluelinelabs/Conductor’s Controller
Creating a Custom Viewgroup
<?xml version="1.0" encoding="utf-8"?>
<com.zhuinden.simplestackdemoexample.common.FirstView
xmlns:android="http://schemas.android.com/..."
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/first_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Go to next"/>
</com.zhuinden.simplestackdemoexample.common.FirstView>
public class FirstView extends RelativeLayout {
/* constructors call init(); */
Backstack backstack;
FirstKey firstKey;
private void init(Context context) {
if(!isInEditMode()) {
backstack = BackstackService.get(context);
firstKey = Backstack.getKey(context);
}
}
@OnClick(R.id.first_button)
public void clickButton(View view) {
backstack.goTo(SecondKey.create());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
ButterKnife.bind(this);
}
/* onAttachedToWindow, onDetachedFromWindow */
}
Step-by-step handling view navigation
(Simple-Stack)
@BindView(R.id.root)
RelativeLayout root;
@Override
public void handleStateChange(StateChange stateChange,
Callback completionCallback) {
if(stateChange.topNewState().equals(stateChange.topPreviousState())) {
completionCallback.stateChangeComplete();
return;
}
backstackDelegate.persistViewToState(root.getChildAt(0));
root.removeAllViews();
Key newKey = stateChange.topNewState();
Context newContext = stateChange.createContext(this, newKey);
View view = LayoutInflater.from(newContext)
.inflate(newKey.layout(), root, false);
backstackDelegate.restoreViewFromState(view);
root.addView(view);
completionCallback.stateChangeComplete();
}
// + lifecycle integration callbacks!
( onCreate(), onPostResume(), onPause(),
onRetainCustomNonConfigurationInstance(), onDestroy() )
Same thing using library defaults
Navigator.install(this, root,
HistoryBuilder.single(FirstKey.create()));
Or showing off some configuration builders...
Navigator.configure()
.setStateChanger(DefaultStateChanger
.configure()
.create(this, root))
.install(this, root,
HistoryBuilder.single(FirstKey.create()));
Navigation using the backstack
backstack.goTo(TaskDetailsKey.create(taskId));
backstack.goBack();
backstack.setHistory(
HistoryBuilder.from(backstack)
.removeLast()
.add(TaskDetailsKey.create(taskId))
.build(), StateChange.REPLACE);
backstack.setHistory(
HistoryBuilder.single(TasksKey.create()),
StateChange.BACKWARD);
backstack.setHistory(
HistoryBuilder.from(
TasksKey.create(), TaskDetailKey.create(taskId)),
StateChange.FORWARD);
But what about Fragments?
• Fragments are also ViewControllers
• Activity provides them with lifecycle integration
out of the box
• FragmentManager keeps track of them and their
state transitions
• All added fragments are recreated after process
death by super.onCreate() in Activity
• (Supports nesting out of the box... with caveats)
Fragment Ops beyond „replace”
• Other useful operators of FragmentTransaction:
– Add/remove:
• Create fragment and its view hierarchy
• Destroy view hierarchy, and fragment as well
– Attach/detach:
• Restore view state, (re-)create view hierarchy
• preserve view state, but destroy view hierarchy
– commitNow():
• Execute the fragment transaction synchronously
• Note: this method cannot be used alongside addToBackStack()
• Using these operators, we can combine this with a custom
backstack, by keeping the fragments and their state alive,
but only the currently visible view hierarchy.
Key for the Fragment
public abstract class BaseKey implements Key {
@Override
public String getFragmentTag() {
return toString();
}
@Override
public final BaseFragment newFragment() {
BaseFragment fragment = createFragment();
Bundle bundle = fragment.getArguments();
if (bundle == null) {
bundle = new Bundle();
}
bundle.putParcelable("KEY", this); // => <T> T getKey() { … }
fragment.setArguments(bundle);
return fragment;
}
protected abstract BaseFragment createFragment();
}
Handling state change with Fragments
• Remove all Fragments that were in previous
state, but are no longer in the new state
(if they are still in new state, then just detach
them)
• Create and add all fragments that are in the new
state and not yet added
• In the new state, if the current top already exists
but is detached, then attach it, if it doesn’t exist,
then create it and add it
(and detach all other non-top fragments)
• Commit transaction now
public class FragmentStateChanger {
public void handleStateChange(StateChange stateChange) {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
for(Object _oldKey : stateChange.getPreviousState()) {
Key oldKey = (Key) _oldKey;
Fragment fragment = fragmentManager.findFragmentByTag(oldKey.getFragmentTag());
if(fragment != null) {
if(!stateChange.getNewState().contains(oldKey)) {
fragmentTransaction.remove(fragment);
} else if(!fragment.isDetached()) {
fragmentTransaction.detach(fragment);
}
}
}
for(Object _newKey : stateChange.getNewState()) {
Key newKey = (Key) _newKey;
Fragment fragment = fragmentManager.findFragmentByTag(newKey.getFragmentTag());
if(newKey.equals(stateChange.topNewState())) {
if(fragment != null && fragment.isDetached()) {
fragmentTransaction.attach(fragment);
} else {
fragment = newKey.createFragment();
fragmentTransaction.add(containerId, fragment, newKey.getFragmentTag());
}
} else if(fragment != null && !fragment.isDetached()) {
fragmentTransaction.detach(fragment);
}
}
fragmentTransaction.commitNow();
}
}
Navigation using the backstack
(with fragments)
backstack.goTo(TaskDetailsKey.create(taskId));
backstack.goBack();
backstack.setHistory(
HistoryBuilder.from(backstack)
.removeLast()
.add(TaskDetailsKey.create(taskId))
.build(), StateChange.REPLACE);
backstack.setHistory(
HistoryBuilder.single(TasksKey.create()),
StateChange.BACKWARD);
backstack.setHistory(
HistoryBuilder.from(
TasksKey.create(), TaskDetailKey.create(taskId)),
StateChange.FORWARD);
DEMO
Navigation-Example
Additional resources
Advocating against Android Fragments
Simpler Apps with Flow and Mortar
Michael Yotive: State of Fragments in 2017
Simplified Fragment Navigation using a custom
backstack
Thank you for your attention!
Q/A?

More Related Content

What's hot

Introduction to React & Redux
Introduction to React & ReduxIntroduction to React & Redux
Introduction to React & ReduxBoris Dinkevich
 
React + Redux. Best practices
React + Redux.  Best practicesReact + Redux.  Best practices
React + Redux. Best practicesClickky
 
State Models for React with Redux
State Models for React with ReduxState Models for React with Redux
State Models for React with ReduxStephan Schmidt
 
Better React state management with Redux
Better React state management with ReduxBetter React state management with Redux
Better React state management with ReduxMaurice De Beijer [MVP]
 
Let's discover React and Redux with TypeScript
Let's discover React and Redux with TypeScriptLet's discover React and Redux with TypeScript
Let's discover React and Redux with TypeScriptMathieu Savy
 
Evan Schultz - Angular Summit - 2016
Evan Schultz - Angular Summit - 2016Evan Schultz - Angular Summit - 2016
Evan Schultz - Angular Summit - 2016Evan Schultz
 
Advanced Dagger talk from 360andev
Advanced Dagger talk from 360andevAdvanced Dagger talk from 360andev
Advanced Dagger talk from 360andevMike Nakhimovich
 
"Migrate large gwt applications - Lessons Learned" By Harald Pehl
"Migrate large gwt applications - Lessons Learned" By Harald Pehl"Migrate large gwt applications - Lessons Learned" By Harald Pehl
"Migrate large gwt applications - Lessons Learned" By Harald PehlGWTcon
 
Adding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsAdding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsJeff Durta
 
Workshop 20: ReactJS Part II Flux Pattern & Redux
Workshop 20: ReactJS Part II Flux Pattern & ReduxWorkshop 20: ReactJS Part II Flux Pattern & Redux
Workshop 20: ReactJS Part II Flux Pattern & ReduxVisual Engineering
 
Redux with angular 2 - workshop 2016
Redux with angular 2 - workshop 2016Redux with angular 2 - workshop 2016
Redux with angular 2 - workshop 2016Nir Kaufman
 
Reactive.architecture.with.Angular
Reactive.architecture.with.AngularReactive.architecture.with.Angular
Reactive.architecture.with.AngularEvan Schultz
 
Android basic 4 Navigation Drawer
Android basic 4 Navigation DrawerAndroid basic 4 Navigation Drawer
Android basic 4 Navigation DrawerEakapong Kattiya
 
Web components with java by Haijian Wang
Web components with java by Haijian WangWeb components with java by Haijian Wang
Web components with java by Haijian WangGWTcon
 
Workshop 26: React Native - The Native Side
Workshop 26: React Native - The Native SideWorkshop 26: React Native - The Native Side
Workshop 26: React Native - The Native SideVisual Engineering
 
Developing New Widgets for your Views in Owl
Developing New Widgets for your Views in OwlDeveloping New Widgets for your Views in Owl
Developing New Widgets for your Views in OwlOdoo
 

What's hot (20)

Introduction to React & Redux
Introduction to React & ReduxIntroduction to React & Redux
Introduction to React & Redux
 
Let's Redux!
Let's Redux!Let's Redux!
Let's Redux!
 
React + Redux. Best practices
React + Redux.  Best practicesReact + Redux.  Best practices
React + Redux. Best practices
 
State Models for React with Redux
State Models for React with ReduxState Models for React with Redux
State Models for React with Redux
 
Better React state management with Redux
Better React state management with ReduxBetter React state management with Redux
Better React state management with Redux
 
Let's discover React and Redux with TypeScript
Let's discover React and Redux with TypeScriptLet's discover React and Redux with TypeScript
Let's discover React and Redux with TypeScript
 
The Road To Redux
The Road To ReduxThe Road To Redux
The Road To Redux
 
Evan Schultz - Angular Summit - 2016
Evan Schultz - Angular Summit - 2016Evan Schultz - Angular Summit - 2016
Evan Schultz - Angular Summit - 2016
 
Advanced Dagger talk from 360andev
Advanced Dagger talk from 360andevAdvanced Dagger talk from 360andev
Advanced Dagger talk from 360andev
 
"Migrate large gwt applications - Lessons Learned" By Harald Pehl
"Migrate large gwt applications - Lessons Learned" By Harald Pehl"Migrate large gwt applications - Lessons Learned" By Harald Pehl
"Migrate large gwt applications - Lessons Learned" By Harald Pehl
 
Adding a modern twist to legacy web applications
Adding a modern twist to legacy web applicationsAdding a modern twist to legacy web applications
Adding a modern twist to legacy web applications
 
Workshop 20: ReactJS Part II Flux Pattern & Redux
Workshop 20: ReactJS Part II Flux Pattern & ReduxWorkshop 20: ReactJS Part II Flux Pattern & Redux
Workshop 20: ReactJS Part II Flux Pattern & Redux
 
Intro to ReactJS
Intro to ReactJSIntro to ReactJS
Intro to ReactJS
 
Redux with angular 2 - workshop 2016
Redux with angular 2 - workshop 2016Redux with angular 2 - workshop 2016
Redux with angular 2 - workshop 2016
 
Angular redux
Angular reduxAngular redux
Angular redux
 
Reactive.architecture.with.Angular
Reactive.architecture.with.AngularReactive.architecture.with.Angular
Reactive.architecture.with.Angular
 
Android basic 4 Navigation Drawer
Android basic 4 Navigation DrawerAndroid basic 4 Navigation Drawer
Android basic 4 Navigation Drawer
 
Web components with java by Haijian Wang
Web components with java by Haijian WangWeb components with java by Haijian Wang
Web components with java by Haijian Wang
 
Workshop 26: React Native - The Native Side
Workshop 26: React Native - The Native SideWorkshop 26: React Native - The Native Side
Workshop 26: React Native - The Native Side
 
Developing New Widgets for your Views in Owl
Developing New Widgets for your Views in OwlDeveloping New Widgets for your Views in Owl
Developing New Widgets for your Views in Owl
 

Similar to Architecting Single Activity Applications (With or Without Fragments)

Anatomy of android application
Anatomy of android applicationAnatomy of android application
Anatomy of android applicationNikunj Dhameliya
 
04 activities - Android
04   activities - Android04   activities - Android
04 activities - AndroidWingston
 
Lecture #4 activities &amp; fragments
Lecture #4  activities &amp; fragmentsLecture #4  activities &amp; fragments
Lecture #4 activities &amp; fragmentsVitali Pekelis
 
02 programmation mobile - android - (activity, view, fragment)
02 programmation mobile - android - (activity, view, fragment)02 programmation mobile - android - (activity, view, fragment)
02 programmation mobile - android - (activity, view, fragment)TECOS
 
iOS app dev Training - Session1
iOS app dev Training - Session1iOS app dev Training - Session1
iOS app dev Training - Session1Hussain Behestee
 
Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3
Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3
Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3Paris Android User Group
 
From Legacy to Hexagonal (An Unexpected Android Journey)
From Legacy to Hexagonal (An Unexpected Android Journey)From Legacy to Hexagonal (An Unexpected Android Journey)
From Legacy to Hexagonal (An Unexpected Android Journey)Jose Manuel Pereira Garcia
 
Android development Training Programme Day 2
Android development Training Programme Day 2Android development Training Programme Day 2
Android development Training Programme Day 2DHIRAJ PRAVIN
 
Presentation on Android application life cycle and saved instancestate
Presentation on Android application life cycle and saved instancestatePresentation on Android application life cycle and saved instancestate
Presentation on Android application life cycle and saved instancestateOsahon Gino Ediagbonya
 
11.11.2020 - Unit 5-3 ACTIVITY, MENU AND SQLITE DATABASE.pptx
11.11.2020 - Unit 5-3  ACTIVITY, MENU AND SQLITE DATABASE.pptx11.11.2020 - Unit 5-3  ACTIVITY, MENU AND SQLITE DATABASE.pptx
11.11.2020 - Unit 5-3 ACTIVITY, MENU AND SQLITE DATABASE.pptxMugiiiReee
 

Similar to Architecting Single Activity Applications (With or Without Fragments) (20)

Fragment
Fragment Fragment
Fragment
 
Anatomy of android application
Anatomy of android applicationAnatomy of android application
Anatomy of android application
 
Android Basic Components
Android Basic ComponentsAndroid Basic Components
Android Basic Components
 
04 activities - Android
04   activities - Android04   activities - Android
04 activities - Android
 
Lecture #4 activities &amp; fragments
Lecture #4  activities &amp; fragmentsLecture #4  activities &amp; fragments
Lecture #4 activities &amp; fragments
 
Android
AndroidAndroid
Android
 
02 programmation mobile - android - (activity, view, fragment)
02 programmation mobile - android - (activity, view, fragment)02 programmation mobile - android - (activity, view, fragment)
02 programmation mobile - android - (activity, view, fragment)
 
iOS app dev Training - Session1
iOS app dev Training - Session1iOS app dev Training - Session1
iOS app dev Training - Session1
 
Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3
Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3
Introduction to Honeycomb APIs - Android Developer Lab 2011 Q3
 
From Legacy to Hexagonal (An Unexpected Android Journey)
From Legacy to Hexagonal (An Unexpected Android Journey)From Legacy to Hexagonal (An Unexpected Android Journey)
From Legacy to Hexagonal (An Unexpected Android Journey)
 
Android development Training Programme Day 2
Android development Training Programme Day 2Android development Training Programme Day 2
Android development Training Programme Day 2
 
Android 3
Android 3Android 3
Android 3
 
Os Haase
Os HaaseOs Haase
Os Haase
 
Presentation on Android application life cycle and saved instancestate
Presentation on Android application life cycle and saved instancestatePresentation on Android application life cycle and saved instancestate
Presentation on Android application life cycle and saved instancestate
 
fragments-activity.pptx
fragments-activity.pptxfragments-activity.pptx
fragments-activity.pptx
 
11.11.2020 - Unit 5-3 ACTIVITY, MENU AND SQLITE DATABASE.pptx
11.11.2020 - Unit 5-3  ACTIVITY, MENU AND SQLITE DATABASE.pptx11.11.2020 - Unit 5-3  ACTIVITY, MENU AND SQLITE DATABASE.pptx
11.11.2020 - Unit 5-3 ACTIVITY, MENU AND SQLITE DATABASE.pptx
 
Activity
ActivityActivity
Activity
 
Activity
ActivityActivity
Activity
 
Activity
ActivityActivity
Activity
 
Activity
ActivityActivity
Activity
 

Recently uploaded

Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantAxelRicardoTrocheRiq
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️Delhi Call girls
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about usDynamic Netsoft
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...OnePlan Solutions
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
 
Clustering techniques data mining book ....
Clustering techniques data mining book ....Clustering techniques data mining book ....
Clustering techniques data mining book ....ShaimaaMohamedGalal
 
Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfCionsystems
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerThousandEyes
 

Recently uploaded (20)

Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service Consultant
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about us
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
Clustering techniques data mining book ....
Clustering techniques data mining book ....Clustering techniques data mining book ....
Clustering techniques data mining book ....
 
Exploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the ProcessExploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the Process
 
Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdf
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 

Architecting Single Activity Applications (With or Without Fragments)

  • 1. Architecting Single-Activity Applications With or without Fragments Gabor Varadi @zhuinden
  • 2. What do we think we know about Activities? From https://developer.android.com/guide/components/intents-filters.html „Starting an activity An Activity represents a single screen in an app. You can start a new instance of an Activity by passing an Intent to startActivity(). The Intent describes the activity to start and carries any necessary data.”
  • 3. What do we think we know about Fragments? From https://developer.android.com/guide/components/fragments.html „A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities.”
  • 4. What is the truth?
  • 5. Dianne Hackborn „How should I design my Android application?” Activity Once we have gotten in to this entry-point to your UI, we really don't care how you organize the flow inside. Make it all one activity with manual changes to its views, use fragments (a convenience framework we provide) or some other framework, or split it into additional internal activities. Or do all three as needed. As long as you are following the high-level contract of activity (it launches in the proper state, and saves/restores in the current state), it doesn't matter to the system.
  • 6. What does that tell us? • Activities are not „screens”, they are entry points to the app (like a main function) • The high-level Activity contract is showing UI for current state, handling initial state, and persist state across configuration change and process death • Fragments are just a „convenience framework” — technically they are ViewControllers with lifecycle integration • Android does NOT care how you handle the flow inside your application!
  • 7. Wait, process death? • Step 1: put app in background with HOME • Step 2: press „Terminate application” • Step 3: restart app from launcher • Step 4: enjoy strange behavior  (app restart, statics are cleared, savedInstanceState != null)
  • 8. What IS the flow inside your application? • Navigation – where you are in your application (and what to show) – where you came from, back/up navigation – remembering navigation state across config change and process death • Scoping – what data needs to be shown – what services need to exist (singleton and subscopes) – how to keep scoped services alive across config change
  • 9. Some magic tricks (that we need to understand first)
  • 10. Passing objects through the context hierarchy: getSystemService() trick • Any object can be exposed via the Context hierarchy by overriding getSystemService() • Objects from Activity (and the activity!) can be exposed directly via Activity.getSystemService() • Objects in subscope of Activity can be exposed through ContextWrapper.getSystemService() by inflating the view with a cloned layout inflater LayoutInflater.from(baseContext) .cloneInContext(contextWrapper);
  • 11. Exposing objects through the Context from the Activity public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; public static MainActivity get(Context context) { // noinspection ResourceType return (MainActivity)context.getSystemService(TAG); } @Override public Object getSystemService(String name) { if(TAG.equals(name)) { return this; // <-- now MainActivity.get(context) works } return super.getSystemService(name); } }
  • 12. public class KeyContextWrapper extends ContextWrapper { public static final String TAG = "Backstack.KEY"; LayoutInflater layoutInflater; final Object key; public KeyContextWrapper(Context base, @NonNull Object key) { super(base); this.key = key; } public static <T> T getKey(Context context) { // noinspection ResourceType Object key = context.getSystemService(TAG); // noinspection unchecked return (T) 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(TAG.equals(name)) { return key; // <-- now KeyContextWrapper.getKey(context) works } return super.getSystemService(name); } }
  • 14. Scoping Allowing data and services to exist for the entire duration of when the screen is visible, and not be killed on configuration changes. Child scopes should be able to inherit from their superscope. Things that set out to solve scoping problem: - Activity: onRetainCustomNonConfigurationInstance() - Fragment: retained fragments - Loaders - square/Mortar: MortarScope - lyft/Scoop: Scoop - zhuinden/Service-Tree: ServiceTree - Architectural Components: ViewModel
  • 15. Goal of scoping • The goal is to make sure the data and services exist for as long as the scope • When the scope is destroyed (as it is no longer needed), the data and services are torn down along with it • In advanced use: – scoped data becomes a dependency that is provided to constructor, but obtained asynchronously and observed for changes (LiveData, BehaviorRelay, Observable + RxReplayingShare) – Dagger component is subscoped, and provides the data as scoped dependency – The Dagger component is stored in the scope to survive configuration changes
  • 16. @Subscope @dagger.Component( dependencies = {SingletonComponent.class}, modules = {ChatModule.class}) ) interface ChatComponent { ChatPresenter chatPresenter(); void inject(ChatView chatView); } @dagger.Module static class ChatModule { private final int chatId; @Provides @Subscope Observable<Chat> chat(ChatRepository chatRepository) { return chatRepository.getChat(chatId); } } @Subscope static class ChatPresenter implements Presenter<ChatView> { @Inject ChatPresenter(Observable<Chat> chat) { // ... } }
  • 17. String scopeTag = chatKey.toString(); MortarScope childScope = parentScope.findChild(scopeTag); if (childScope == null) { childScope = parentScope.buildChild() .withService(DaggerService.SERVICE_NAME, key.createComponent(parentScope)) .build(scopeTag); } return childScope; ---------------------------------------------------- ChatComponent component = DaggerService.<ChatComponent>getService(context); ---------------------------------------------------- MortarScope.getScope(context).destroy(); Creating/Destroying Scopes: Mortar
  • 18. The common approach to simplifying the problem • Create only Singleton scope (and a single global injector), everything else is unscoped • Unscoped dependencies have their state persisted to Bundle, and restored if state exists • Also: if the ViewController is preserved even without its view hierarchy, then it can BE the scope! (retained fragments, Conductor’s Controller)
  • 19. Navigation • We must know where we are, remember where we have been • This state must be preserved across configuration changes and process death • Things that set out to solve Navigation problem: – Activity record stack – Fragment backstack – square/flow 0.8 – lyft/scoop – square/flow 1.0-alpha3 – terrakok/Cicerone (no backstack, only command queue) – bluelinelabs/Conductor – zhuinden/simple-stack – wealthfront/magellan (don’t use it – does NOT preserve state across process death!!!)
  • 20. Checklist for what a backstack should be able to do • Handling state persistence across config change / process death • Should receive both the previous and the new state on state change • Animations are asynchronous – operations must be enqueued • State changer is not always available (after onPause) – operations must be enqueued
  • 21. How do Activities handle navigation? • Intents to start new Activities • Parameters are provided in the extras Bundle, generally as a dynamically typed storage with string keys • Intents have „intent flags” to manipulate „task stack” (CLEAR_TOP, REORDER_TO_FRONT, etc.) • Downsides: – You can’t easily tell what Activities exist in the background – Modifying stack needs tricky combinations of intent flags (no fine-grained control) – No notifications about change (previous state, new state) – Complicated lifecycle if multiple Activities exist
  • 22. --- APP START D/MainActivity: onCreate D/MainActivity: onStart D/MainActivity: onResume D/MainActivity: onPostResume --- START SECOND ACTIVITY in onPostResume() D/MainActivity: onPause D/SecondActivity: onCreate D/SecondActivity: onStart D/SecondActivity: onResume D/SecondActivity: onPostResume D/MainActivity: onSaveInstanceState D/MainActivity: onStop --- FINISH SECOND ACTIVITY D/SecondActivity: onPause D/MainActivity: onStart D/MainActivity: onResume D/MainActivity: onPostResume D/SecondActivity: onStop D/SecondActivity: onDestroy
  • 23. How are Fragments generally used for navigation? • FragmentTransactions (begin/commit) • Tutorials typically show „replace()” in conjunction with „addToBackStack()” • The backstack stores transactions (operations) with a tag to pop to (inclusive/exclusive) • (Parameters are also provided as Bundle, called arguments) • Downsides: – onBackstackChanged() provides change notification, but does not provide previous and new states (also it’s kinda random) – Stack stores operations instead of active fragments, so asymmetric navigation is super-difficult – commit() runs transaction on the NEXT event loop (what about onPause? „Cannot perform after onSaveInstanceState()”)
  • 24. Principle of Flow (and its variants) • „Flow” is a custom backstack to store current state and history • Content of the backstack is saved to and restored from Bundle (for process death) as Parcelables • State is represented as immutable parcelable value objects, called Keys • Keys contain all necessary data in order to set up the initial state (like Intent extras), but as typed values of the class • List of previous / new keys are both available („Traversal”,…)
  • 25. @AutoValue public abstract class TaskDetailKey implements Key, Parcelable { public abstract String taskId(); // <- instead of static final TASK_ID = „TASK_ID”; public static TaskDetailKey create(String taskId) { return new AutoValue_TaskDetailKey(R.layout.path_taskdetail, taskId); } @Override public int menu() { return R.menu.taskdetail_fragment_menu; } @Override public boolean isFabVisible() { return true; } @Override public View.OnClickListener fabClickListener(View view) { return v -> { ((TaskDetailView)view).editTask(); }; } @Override public int fabDrawableIcon() { return R.drawable.ic_edit; } }
  • 26. @PaperParcel data class TaskDetailKey(val taskId: String) : Key, PaperParcelable { override fun layout() = R.layout.task_detail override fun menu() = R.menu.taskdetail_fragment_menu override fun isFabVisible() = true override fun fabClickListener(view: View) { return View.OnClickListener { v -> (view as SecondView).editTask() } } override fun fabDrawableIcon() = R.drawable.ic_edit companion object { @JvmField val CREATOR = PaperParcelTaskDetailKey.CREATOR } }
  • 27. public void setupViewsForKey(Key key, View newView) { if(key.shouldShowUp()) { setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED, GravityCompat.START); MainActivity.get(getContext()).getSupportActionBar() .setDisplayHomeAsUpEnabled(true); drawerToggle.setDrawerIndicatorEnabled(false); } else { setDrawerLockMode(LOCK_MODE_UNLOCKED, GravityCompat.START); MainActivity.get(getContext()).getSupportActionBar() .setDisplayHomeAsUpEnabled(false); drawerToggle.setDrawerIndicatorEnabled(true); } drawerToggle.syncState(); setCheckedItem(key.navigationViewId()); MainActivity.get(getContext()).supportInvalidateOptionsMenu(); if(key.isFabVisible()) { fabAddTask.setVisibility(View.VISIBLE); } else { fabAddTask.setVisibility(View.GONE); } fabAddTask.setOnClickListener(key.fabClickListener(newView)); if(key.fabDrawableIcon() != 0) { fabAddTask.setImageResource(key.fabDrawableIcon()); } } Example: set up view by key
  • 28. Displaying the View for a given Key • Our Key specifies what we want to show • We need to handle the events of the views (clicks, text changes, etc.) • For that, we need a „ViewController” (which can be used inside an Activity, so not an Activity) • Possible options: – Custom ViewGroup – Fragment – lyft/scoop’s ViewController – square/coordinators’s Coordinator – bluelinelabs/Conductor’s Controller
  • 29. Creating a Custom Viewgroup <?xml version="1.0" encoding="utf-8"?> <com.zhuinden.simplestackdemoexample.common.FirstView xmlns:android="http://schemas.android.com/..." android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/first_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Go to next"/> </com.zhuinden.simplestackdemoexample.common.FirstView>
  • 30. public class FirstView extends RelativeLayout { /* constructors call init(); */ Backstack backstack; FirstKey firstKey; private void init(Context context) { if(!isInEditMode()) { backstack = BackstackService.get(context); firstKey = Backstack.getKey(context); } } @OnClick(R.id.first_button) public void clickButton(View view) { backstack.goTo(SecondKey.create()); } @Override protected void onFinishInflate() { super.onFinishInflate(); ButterKnife.bind(this); } /* onAttachedToWindow, onDetachedFromWindow */ }
  • 31. Step-by-step handling view navigation (Simple-Stack) @BindView(R.id.root) RelativeLayout root; @Override public void handleStateChange(StateChange stateChange, Callback completionCallback) { if(stateChange.topNewState().equals(stateChange.topPreviousState())) { completionCallback.stateChangeComplete(); return; } backstackDelegate.persistViewToState(root.getChildAt(0)); root.removeAllViews(); Key newKey = stateChange.topNewState(); Context newContext = stateChange.createContext(this, newKey); View view = LayoutInflater.from(newContext) .inflate(newKey.layout(), root, false); backstackDelegate.restoreViewFromState(view); root.addView(view); completionCallback.stateChangeComplete(); } // + lifecycle integration callbacks! ( onCreate(), onPostResume(), onPause(), onRetainCustomNonConfigurationInstance(), onDestroy() )
  • 32. Same thing using library defaults Navigator.install(this, root, HistoryBuilder.single(FirstKey.create())); Or showing off some configuration builders... Navigator.configure() .setStateChanger(DefaultStateChanger .configure() .create(this, root)) .install(this, root, HistoryBuilder.single(FirstKey.create()));
  • 33. Navigation using the backstack backstack.goTo(TaskDetailsKey.create(taskId)); backstack.goBack(); backstack.setHistory( HistoryBuilder.from(backstack) .removeLast() .add(TaskDetailsKey.create(taskId)) .build(), StateChange.REPLACE); backstack.setHistory( HistoryBuilder.single(TasksKey.create()), StateChange.BACKWARD); backstack.setHistory( HistoryBuilder.from( TasksKey.create(), TaskDetailKey.create(taskId)), StateChange.FORWARD);
  • 34. But what about Fragments? • Fragments are also ViewControllers • Activity provides them with lifecycle integration out of the box • FragmentManager keeps track of them and their state transitions • All added fragments are recreated after process death by super.onCreate() in Activity • (Supports nesting out of the box... with caveats)
  • 35. Fragment Ops beyond „replace” • Other useful operators of FragmentTransaction: – Add/remove: • Create fragment and its view hierarchy • Destroy view hierarchy, and fragment as well – Attach/detach: • Restore view state, (re-)create view hierarchy • preserve view state, but destroy view hierarchy – commitNow(): • Execute the fragment transaction synchronously • Note: this method cannot be used alongside addToBackStack() • Using these operators, we can combine this with a custom backstack, by keeping the fragments and their state alive, but only the currently visible view hierarchy.
  • 36. Key for the Fragment public abstract class BaseKey implements Key { @Override public String getFragmentTag() { return toString(); } @Override public final BaseFragment newFragment() { BaseFragment fragment = createFragment(); Bundle bundle = fragment.getArguments(); if (bundle == null) { bundle = new Bundle(); } bundle.putParcelable("KEY", this); // => <T> T getKey() { … } fragment.setArguments(bundle); return fragment; } protected abstract BaseFragment createFragment(); }
  • 37. Handling state change with Fragments • Remove all Fragments that were in previous state, but are no longer in the new state (if they are still in new state, then just detach them) • Create and add all fragments that are in the new state and not yet added • In the new state, if the current top already exists but is detached, then attach it, if it doesn’t exist, then create it and add it (and detach all other non-top fragments) • Commit transaction now
  • 38. public class FragmentStateChanger { public void handleStateChange(StateChange stateChange) { FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); for(Object _oldKey : stateChange.getPreviousState()) { Key oldKey = (Key) _oldKey; Fragment fragment = fragmentManager.findFragmentByTag(oldKey.getFragmentTag()); if(fragment != null) { if(!stateChange.getNewState().contains(oldKey)) { fragmentTransaction.remove(fragment); } else if(!fragment.isDetached()) { fragmentTransaction.detach(fragment); } } } for(Object _newKey : stateChange.getNewState()) { Key newKey = (Key) _newKey; Fragment fragment = fragmentManager.findFragmentByTag(newKey.getFragmentTag()); if(newKey.equals(stateChange.topNewState())) { if(fragment != null && fragment.isDetached()) { fragmentTransaction.attach(fragment); } else { fragment = newKey.createFragment(); fragmentTransaction.add(containerId, fragment, newKey.getFragmentTag()); } } else if(fragment != null && !fragment.isDetached()) { fragmentTransaction.detach(fragment); } } fragmentTransaction.commitNow(); } }
  • 39. Navigation using the backstack (with fragments) backstack.goTo(TaskDetailsKey.create(taskId)); backstack.goBack(); backstack.setHistory( HistoryBuilder.from(backstack) .removeLast() .add(TaskDetailsKey.create(taskId)) .build(), StateChange.REPLACE); backstack.setHistory( HistoryBuilder.single(TasksKey.create()), StateChange.BACKWARD); backstack.setHistory( HistoryBuilder.from( TasksKey.create(), TaskDetailKey.create(taskId)), StateChange.FORWARD);
  • 41. Additional resources Advocating against Android Fragments Simpler Apps with Flow and Mortar Michael Yotive: State of Fragments in 2017 Simplified Fragment Navigation using a custom backstack
  • 42. Thank you for your attention! Q/A?

Editor's Notes

  1. Communication between same-level components => superscoped service with change listeners
  2. Flow: Traversal, Scoop: RouteChange, Simple-Stack: StateChange, Conductor: RouterTransaction Keys were also once called „Screen”
  3. This code runs for the current top state, both on forward AND back navigation!
  4. Mortar’s ViewPresenter was responsible for Custom Viewgroup’s onSaveInstanceState() integration. It wasn’t particularly good though and could result in very hard to fix state persistence bugs.
  5. Touchlab: Advanced RxJava + Conductor - https://www.youtube.com/watch?v=0XSf7sX2rCQ