{"id":18255467,"url":"https://github.com/roroche/androidmodularsample","last_synced_at":"2025-04-04T17:31:23.000Z","repository":{"id":217038120,"uuid":"76166782","full_name":"RoRoche/AndroidModularSample","owner":"RoRoche","description":"A sample Android application to demonstrate how to build screens as fully independant modules.","archived":false,"fork":false,"pushed_at":"2020-04-26T16:49:47.000Z","size":670,"stargazers_count":16,"open_issues_count":1,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-01-14T07:50:46.960Z","etag":null,"topics":["android-application","conductor","dagger2","easyflow","fsm","independant-modules","navigation","state-machine"],"latest_commit_sha":null,"homepage":"https://roroche.github.io/AndroidModularSample/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RoRoche.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-12-11T10:09:24.000Z","updated_at":"2024-01-14T07:50:56.435Z","dependencies_parsed_at":"2024-01-14T08:05:04.962Z","dependency_job_id":null,"html_url":"https://github.com/RoRoche/AndroidModularSample","commit_stats":null,"previous_names":["roroche/androidmodularsample"],"tags_count":0,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoRoche%2FAndroidModularSample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoRoche%2FAndroidModularSample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoRoche%2FAndroidModularSample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RoRoche%2FAndroidModularSample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RoRoche","download_url":"https://codeload.github.com/RoRoche/AndroidModularSample/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223150654,"owners_count":17095959,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android-application","conductor","dagger2","easyflow","fsm","independant-modules","navigation","state-machine"],"created_at":"2024-11-05T10:16:32.761Z","updated_at":"2024-11-05T10:16:33.435Z","avatar_url":"https://github.com/RoRoche.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AndroidModularSample\n\n[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-AndroidModularSample-brightgreen.svg?style=flat)](http://android-arsenal.com/details/3/4810)\n[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-%23266-green.svg)](http://androidweekly.net/issues/issue-266)\n\n![logo](https://raw.githubusercontent.com/RoRoche/AndroidModularSample/master/assets/logo.png)\n\n\u003c!-- run the following command line: markdown-toc -i README.md --\u003e\n\n\u003c!-- toc --\u003e\n\n- [FSM on Android: how to reach app modularity?](#fsm-on-android-how-to-reach-app-modularity)\n  * [Introduction](#introduction)\n  * [FSM to manage view states](#fsm-to-manage-view-states)\n  * [FSM to manage app navigation](#fsm-to-manage-app-navigation)\n    + [Why Conductor?](#why-conductor)\n    + [\"Screens\" as \"States\"](#screens-as-states)\n    + [Navigation between screens](#navigation-between-screens)\n  * [FSM to make app modular](#fsm-to-make-app-modular)\n    + [Screns as independent modules](#screns-as-independent-modules)\n    + [DI ready: example with Dagger 2](#di-ready-example-with-dagger-2)\n  * [Conclusion](#conclusion)\n  * [Bibliography](#bibliography)\n  * [Logo credits](#logo-credits)\n\n\u003c!-- tocstop --\u003e\n\n# FSM on Android: how to reach app modularity?\n\n## Introduction\n\nIn this document, I want to explain how I ended up with a FSM as a solution to make Android application development fully-modular. That's why this document is organized in steps, describing the journey I made, from discovering the [EasyFlow](https://github.com/Beh01der/EasyFlow) FSM to using it for modularity purposes.\n\nWhen talking about modularity, all developers agree on a standard definition:\n\n- divide a program into separated sub-programs (called modules) according to the features to implement,\n- group similar functions in the same unit of code.\n\nAdvantages:\n\n- ease incremental builds and deliveries,\n- module is unit-testable,\n- modules can be added, modified or removed without any impact on one another,\n- modules can be reused.\n\nBut on some implementations, I didn't agree with some choices. For example, to navigate to the next screen, a module knows the name of this next screen thanks to a constant (from the hosting application or from the next module itself): this introduces a high-coupling that stinks. To my mind, to be fully independant and agnostic, a module must not depend on a constant from anywhere else. At best, it exposes constants to be used by the hosting application. So, how to transit from one screen to the next one? The answer became clear: thanks to events. Indeed, events bring the needed abstraction: the module fires an event, the hosting application receives this event and acts accordingly. This is how I discovered the \"event-driven development\" paradigm.\n\nAccording to this paradigm, the flow of the program is determined by events (user actions, network requests, sensors, timer, other threads, etc.). A specific piece of code (often called the \"event listener\" or \"main loop\" according to the language) detects that an event has occurred and performs the corresponding event \"handler\" (a callback method for example). The resulting computation changes the program state.\n\nFinally, keeping in mind my final goal, I remembered the terms used by the Android SDK: when an `Activity` is destroyed, it mentions the fact of saving and restoring the \"instance state\". And all became clear for me: a \"screen\" can be considered as a \"state\" of the application, and navigation is somehow a finite state machine.\n\nA finite state machine (FSM) is defined by:\n\n- sequential logic circuits,\n- finite number of states,\n- one state at a time (the _current state_),\n- change from one state to another by triggering an event (a _transition_).\n\nFor Java, the implementation I've chosen is [EasyFlow](https://github.com/Beh01der/EasyFlow), because:\n\n- it's very simple to set up,\n- it's possible to define a global context (useful to pass arguments for example),\n- states are easily defined by declaring an _enum_ implementing `StateEnum`,\n- events are defined easily by declaring an _enum_ implementing `EventEnum`,\n- it provides a fluent API, which allows developers to declare the state machine in a clear and intelligible way (especially when combined with lambda expressions),\n- it's possible to declare callbacks to perform specific jobs when entering or leaving a state.\n\n## FSM to manage view states\n\nLet's consider a typical example of a simple screen loading data from the network to display the result. Below we can see a state diagram corresponding to this screen.\n\n```mermaid\ngraph LR\n    Idle --\u003e|on first load| Loading\n    Loading --\u003e|on error| Error\n    Loading --\u003e|on success| Content\n    Content --\u003e|on refresh| Loading\n    Error --\u003e|on retry| Loading\n```\n\n![View states diagram](https://raw.githubusercontent.com/RoRoche/AndroidModularSample/master/docs/diagrams/view_states.mmd.png)\n\nThe associated XML layout is not very relevant at this point. But just consider the following declarations in the `Activity` are valid:\n\n```java\n@BindView(R.id.ActivityMain_TextView_Content)\nTextView mTextViewContent;\n@BindView(R.id.ActivityMain_ProgressBar)\nProgressBar mProgressBar;\n@BindView(R.id.ActivityMain_ViewGroup_Content)\nLinearLayout mViewGroupContent;\n@BindView(R.id.ActivityMain_ViewGroup_Error)\nLinearLayout mViewGroupError;\n```\n\nNow, using the great library called [Switcher](https://github.com/jkwiecien/Switcher), we can define some fields as follows:\n\n```java\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n\n    mUnbinder = ButterKnife.bind(this);\n\n    mSwitcher = new Switcher.Builder(this)\n            .addContentView(mViewGroupContent)\n            .addErrorView(mViewGroupError)\n            .addProgressView(mProgressBar)\n            .build();\n```\n\nNow, to setup the FSM corresponding to the state diagram, we need to define the available `States`, `Events` and `Context` as follows:\n\n```java\nenum States implements StateEnum {\n    LOADING, ERROR, CONTENT\n}\n\nenum Events implements EventEnum {\n    onError, onSuccess, onRetry, onRefresh\n}\n\nclass FlowContext extends StatefulContext {\n}\n```\n\nAnd then we can set up our FSM programmatically as exposed below:\n\n```java\nEasyFlow\u003cFlowContext\u003e flow =\n        from(States.LOADING).transit(\n                 on(Events.onError).to(States.ERROR).transit(\n                        on(Events.onRetry).to(States.LOADING)\n                ),\n                on(Events.onSuccess).to(States.CONTENT).transit(\n                        on(Events.onRefresh).to(States.LOADING)\n                )\n        );\n```\n\nAnd now we have to define what to perform when entering a given state (with the help of the [retrolambda library](https://github.com/evant/gradle-retrolambda)):\n\n```java\nflow.whenEnter(States.LOADING, (final StatefulContext context) -\u003e startRequest());\nflow.whenEnter(States.CONTENT, (final StatefulContext context) -\u003e mSwitcher.showContentView());\nflow.whenEnter(States.ERROR, (final StatefulContext context) -\u003e mSwitcher.showErrorView());\n```\n\nWe can start the FSM as follows:\n\n```java\nflow.executor(new UiThreadExecutor());\nmFlowContext = new FlowContext();\nflow.start(mFlowContext);\n```\n\nThe `Executor` instance allows developpers to configure the type of thread used to perfom FSM operations (such as `whenEnter`). It looks like:\n\n```java\npublic class UiThreadExecutor implements Executor {\n    private Handler mHandler = new Handler(Looper.getMainLooper());\n\n    @Override\n    public void execute(Runnable command) {\n        mHandler.post(command);\n    }\n}\n```\n\nTo perform the network request in this example, I use the [Volley library](https://android.googlesource.com/platform/frameworks/volley/) as follows:\n\n```java\nprivate void startRequest() {\n    mSwitcher.showProgressViewImmediately();\n    StringRequest request = new StringRequest(\n            \"https://api.github.com/users/RoRoche\",\n            (final String response) -\u003e {\n                mTextViewContent.setText(response);\n                try {\n                    mFlowContext.trigger(Events.onSuccess);\n                } catch (LogicViolationError logicViolationError) {\n                    Log.e(TAG, \"startRequest\", logicViolationError);\n                }\n            },\n            (final VolleyError error) -\u003e {\n                try {\n                    mFlowContext.trigger(Events.onError);\n                } catch (LogicViolationError logicViolationError) {\n                    Log.e(TAG, \"startRequest\", logicViolationError);\n                }\n            });\n    mQueue.add(request);\n}\n```\n\nAll that remains for me to do, therefore, is to implement methods listening to user interactions:\n\n```java\n@OnClick(R.id.ActivityMain_Button_Refresh)\npublic void onClickRefresh() {\n    try {\n        mFlowContext.trigger(Events.onRefresh);\n    } catch (LogicViolationError logicViolationError) {\n        Log.e(TAG, \"onClickRefresh\", logicViolationError);\n    }\n}\n\n@OnClick(R.id.ActivityMain_Button_Retry)\npublic void onClickRetry() {\n    try {\n        mFlowContext.trigger(Events.onRetry);\n    } catch (LogicViolationError logicViolationError) {\n        Log.e(TAG, \"onClickRetry\", logicViolationError);\n    }\n}\n```\n\nPutting it all together and it works like a charm! Transitions are logical, fluid and well-defined. The logical code is devoted to the FSM. The rest of the code consists in triggering the suitable event.\n\n## FSM to manage app navigation\n\nFollowing the same logic, I decided to set up 2 screens and manage navigation thanks to a FSM.\n\n### Why Conductor?\n\nFirst of all, it's important to focus on the [Conductor library](https://github.com/bluelinelabs/Conductor) I chose to create a \"View-based application\".\n\nHere are many advantages of using this library:\n\n- Navigation concerns\n- Simple to build an instance of `Controller` and provide its dependencies (no args `Bundle` like when using `Fragment`)\n- Bring forward the `ViewController` concept\n- Pretty transitions\n- Easy to integrate in a MVP or VIPER architecture\n\n### \"Screens\" as \"States\"\n\nThe idea came to me that, in fact, a screen displayed to the user can be considered as a state of the application. I mean: for example, when presenting a screen to log in, it's a state \"waiting for login\", isn't it? And the event to change the app state is \"valid login provided\", isn't it?\n\nThe idea is to use Conductor to define each screen, and use the `Activity` to monitor the interactions and navigation.\n\nTo keep each screen independant, I decided to use the `StatefulContext` implementation to hold arguments as a `Bundle` instance (pretty familiar in the Android world, isn't?). This way, the implementation looks like:\n\n```java\npublic class FlowContext extends StatefulContext {\n    private final Bundle mArgs = new Bundle();\n\n    public Bundle args() {\n        return mArgs;\n    }\n}\n```\n\nNow it's time to design the state diagram of the features to implement:\n\n```mermaid\ngraph LR\n    WaitingForLogin --\u003e|on valid login provided| ShowingWelcome\n    ShowingWelcome --\u003e|on back pressed| WaitingForLogin\n```\n\n![App navigation diagram](https://raw.githubusercontent.com/RoRoche/AndroidModularSample/master/docs/diagrams/app_navigation.mmd.png)\n\nVery simple in fact. So it's time to define our first screen (\"waiting for login\"):\n\n```java\npublic class FirstController extends Controller {\n    //region Constants\n    private static final String ARG_KEY_LOGIN = \"LOGIN\";\n    //endregion\n\n    //region Fields\n    private FlowContext mFlowContext;\n    private TextInputEditText mEditTextLogin;\n    //endregion\n\n    //region Constructors\n    public FirstController() {\n        this(new FlowContext());\n    }\n\n    public FirstController(FlowContext flowContext) {\n        super();\n        mFlowContext = flowContext;\n    }\n    //endregion\n\n    //region Controller\n    @NonNull\n    @Override\n    protected View onCreateView(LayoutInflater inflater, ViewGroup container) {\n        View view = inflater.inflate(R.layout.first_controller, container, false);\n        mEditTextLogin = (TextInputEditText) view.findViewById(R.id.FirstController_EditText_Login);\n        view.findViewById(R.id.FirstController_Button_Start).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View pView) {\n                onClickButtonStart();\n            }\n        });\n        return view;\n    }\n    //endregion\n\n    //region User interaction\n    private void onClickButtonStart() {\n        String login = mEditTextLogin.getText().toString();\n        if (TextUtils.isEmpty(login)) {\n            mEditTextLogin.setError(getApplicationContext().getString(R.string.login_error));\n        } else {\n            try {\n                mFlowContext.args().putString(ARG_KEY_LOGIN, login);\n                mFlowContext.trigger(Events.loginProvided);\n            } catch (final LogicViolationError poLogicViolationError) {\n            }\n        }\n    }\n    //endregion\n\n    //region FSM\n    public enum States implements StateEnum {\n        WAITING_LOGIN\n    }\n\n    public enum Events implements EventEnum {\n        loginProvided\n    }\n\n    public static String getLogin(FlowContext flowContext) {\n        return flowContext.args().getString(ARG_KEY_LOGIN);\n    }\n    //endregion\n}\n```\n\nThis screen defines its available states and events. It's necessary to pass the `FlowContext` to its constructor. After some logical controls, this screen puts the provided value in the `FlowContext` arguments and triggers the `loginProvided` event. It's its single responsability: display an interface to fill a login value, control it and notify of the sequence success.\n\nIn the same way, let's define the second screen (for simplicity, it just displays the provided login):\n\n```java\npublic class SecondController extends Controller {\n    //region Args\n    private final String mLogin;\n    //endregion\n\n    //region Constructors\n    public SecondController() {\n        this(\"\");\n    }\n\n    public SecondController(String login) {\n        super();\n        mLogin = login;\n    }\n    //endregion\n\n    //region Controller\n    @NonNull\n    @Override\n    protected View onCreateView(LayoutInflater inflater, ViewGroup container) {\n        View view = inflater.inflate(R.layout.second_controller, container, false);\n        TextView textViewWelcome = (TextView) view.findViewById(R.id.SecondController_TextView_Welcome);\n        textViewWelcome.setText(getApplicationContext().getString(R.string.welcome, mLogin));\n        return view;\n    }\n    //endregion\n\n    //region FSM\n    public enum States implements StateEnum {\n        SHOWING_WELCOME\n    }\n    //endregion\n}\n```\n\nMuch simpler than the previous one! But now you should ask yourself \"wait! where are these screens built? where is the navigation logic? where is the back event?\". Here is my point: in the `Activity`. No screen should know how to transit from one state to another. They just have to define the state they represent and the events that can be triggered. Moreover, for me, this second screen must not know the \"back pressed\" event. It is not its responsability to navigate back. So, now, it's time to have a look at the `Activity`:\n\n### Navigation between screens\n\n```java\npublic class MainActivity extends AppCompatActivity {\n\n    //region Fields\n    private Router mRouter;\n    private EasyFlow\u003cFlowContext\u003e mFlow;\n    private FlowContext mFlowContext;\n    //endregion\n\n    //region Lifecycle\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n\n        ViewGroup container = (ViewGroup) findViewById(R.id.ViewGroup_Container);\n        mRouter = Conductor.attachRouter(this, container, savedInstanceState);\n\n        mFlow =\n                from(FirstController.States.WAITING_LOGIN).transit(\n                        on(FirstController.Events.loginProvided).to(SecondController.States.SHOWING_WELCOME).transit(\n                                on(Events.backPressed).to(FirstController.States.WAITING_LOGIN)\n                        )\n                );\n\n        mFlow.executor(new UiThreadExecutor());\n\n        mFlow.whenEnter(FirstController.States.WAITING_LOGIN, (FlowContext context) -\u003e {\n            if (!mRouter.hasRootController()) {\n                mRouter.setRoot(RouterTransaction.with(new FirstController(context)));\n            }\n        });\n\n        mFlow.whenEnter(SecondController.States.SHOWING_WELCOME, (FlowContext context) -\u003e {\n            SecondController loController = new SecondController(FirstController.getLogin(context));\n\n            AndroidModularApplication.getInstance().getComponentSecondController().inject(loController);\n\n            RouterTransaction transaction = RouterTransaction.with(loController)\n                    .pushChangeHandler(new FadeChangeHandler())\n                    .popChangeHandler(new FadeChangeHandler());\n\n            mRouter.pushController(transaction);\n        });\n\n        mFlow.whenLeave(SecondController.States.SHOWING_WELCOME, (FlowContext context) -\u003e {\n            context.args().clear();\n        });\n\n        mFlowContext = new FlowContext();\n        mFlow.start(mFlowContext);\n    }\n\n    @Override\n    public void onBackPressed() {\n        try {\n            mFlowContext.trigger(Events.backPressed);\n        } catch (LogicViolationError logicViolationError) {\n        }\n\n        if (!mRouter.handleBack()) {\n            super.onBackPressed();\n        }\n    }\n    //endregion\n\n    //region FSM\n    public enum Events implements EventEnum {\n        backPressed\n    }\n    //endregion\n}\n```\n\nAll the valuable jobs are done in the \"whenEnter\" sequences. Indeed, when entering the first state, we create and push the first screen to the Conductor-specific `Router` instance. When entering the second state, we build the second screen, pass the arguments to it and push it to the `Router` using a fading `RouterTransaction`.\n\nWe override the `onBackPressed` and trigger the corresponding event to update the FSM current state. Then if the `Router` instance allows \"back\" at this level, this event is processed.\n\nFinally, it is not that much complicated to do. But we gain a clean design, with a perfect application of the \"Single responsability principle\". This Android application is now a valuable combination of \"states\" and \"events\".\n\n## FSM to make app modular\n\n### Screns as independent modules\n\nYou know what? It's the simplest step of this article: you just have to create two Android modules (\"first\" and \"second\") thanks to the Android Studio wizard, drag and drop each piece of code in the corresponding module and that's it! Well, not really: you have to create a \"common\" module to place the `FlowContext` and `UiThreadExecutor`. Both \"first\" and \"second\" modules reference this \"common\" module. Now, these modules can be successfully referenced by the application.\n\nToo simple maybe? So let's  go a step further with the dependency injection topic.\n\n### DI ready: example with Dagger 2\n\nNow you have an application divided into multiple screens (states) and responding to various events, you may ask yourself how to define and provide the dependencies needed by your screens.\nTo introduce a solution, I will use the [Dagger 2 library](https://google.github.io/dagger/).\n\nSuppose we want to display the current date on the second screen of our application. I'll start by defining an interface in the \"second\" module:\n\n```java\npublic interface IDateFormatter {\n    String format(Date date);\n}\n```\n\nIn the \"app\" module, I'm going to implement this one:\n\n```java\npublic final class DateFormatter implements IDateFormatter {\n\n    //region Constants\n    private static final String DATE_FORMAT = \"dd/MM/yyyy\";\n    private static final SimpleDateFormat sSimpleDateFormat = new SimpleDateFormat(DATE_FORMAT);\n    //endregion\n\n    //region IDateFormatter\n    @Override\n    public String format(Date date) {\n        return sSimpleDateFormat.format(date);\n    }\n    //endregion\n}\n```\n\nNow, let's create the suitable Dagger 2 `Module` as follows:\n\n```java\n@Module\npublic class ModuleSecondController {\n\n    @Provides\n    @Singleton\n    public IDateFormatter providesDateFormatter() {\n        return new DateFormatter();\n    }\n\n}\n```\n\nAnd the `Component` to configure the second screen:\n\n```java\n@Singleton\n@Component(modules = {ModuleSecondController.class})\npublic interface ComponentSecondController {\n\n    void inject(SecondController secondController);\n\n}\n```\n\nThe next step is to subclass the Android `Application` class and build the `Module` and `Component` as follows:\n\n```java\npublic class AndroidModularApplication extends Application {\n\n    //region Static field\n    private static AndroidModularApplication sInstance;\n    //endregion\n\n    //region Field\n    private ComponentSecondController mComponentSecondController;\n    //endregion\n\n    //region Overridden method\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        sInstance = this;\n        mComponentSecondController = DaggerComponentSecondController.builder()\n                .moduleSecondController(new ModuleSecondController())\n                .build();\n    }\n    //endregion\n\n    //region Static getter\n    public static AndroidModularApplication getInstance() {\n        return sInstance;\n    }\n    //endregion\n\n    //region Getter\n    public ComponentSecondController getComponentSecondController() {\n        return mComponentSecondController;\n    }\n    //endregion\n}\n```\n\nAnd now, the final step takes place in the `MainActivity`. It's time to inject dependencies in the second screen. So we're back to the \"whenEnter\" sequence when building the second screen:\n\n```java\nSecondController controller = new SecondController(FirstController.getLogin(context));\n\nAndroidModularApplication.getInstance().getComponentSecondController().inject(controller);\n\nRouterTransaction transaction = RouterTransaction.with(controller)\n                    .pushChangeHandler(new FadeChangeHandler())\n                    .popChangeHandler(new FadeChangeHandler());\n\nmRouter.pushController(transaction);\n```\n\nEventually, we can add a preconditions to the second screen (defensive programming!) as follows:\n\n```java\nprotected View onCreateView(LayoutInflater inflater, ViewGroup container) {\n    Preconditions.checkNotNull(mDateFormatter, \"Field mDateFormatter is null, did you miss to inject it with your dependency injection mechanism?\");\n```\n\nThis way, you can inject any element you want (data store, user preferences, etc.). But the targetted screen remains agnostic, independent and highly configurable.\n\n## Conclusion\n\n- An easy way to deal with the states of a screen\n- A new approach to set up the navigation flow of an application\n- A solution to build trully reusable modules\n- High level of configuration with DI\n\n- Perspectives: what about a library of existing modules, used by a \"drag \u0026 drop\" Web interface to design an entire application in a user-friendly way?\n\n## Bibliography\n\n- [Applications as State Machines](http://macoscope.com/blog/applications-as-state-machines/)\n\n## Logo credits\n\nFood graphic by \u003ca href=\"http://www.flaticon.com/authors/freepik\"\u003eFreepik\u003c/a\u003e from \u003ca href=\"http://www.flaticon.com/\"\u003eFlaticon\u003c/a\u003e is licensed under \u003ca href=\"http://creativecommons.org/licenses/by/3.0/\" title=\"Creative Commons BY 3.0\"\u003eCC BY 3.0\u003c/a\u003e. Made with \u003ca href=\"http://logomakr.com\" title=\"Logo Maker\"\u003eLogo Maker\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froroche%2Fandroidmodularsample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froroche%2Fandroidmodularsample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froroche%2Fandroidmodularsample/lists"}