Git Product home page Git Product logo

dynamo's Introduction

Android Arsenal Circle CI

DEPRECATED

This project is now deprecated and superseded by:

  • Pilot for Android app architecture
  • Engine for a lightweight Finite State Machine

Dynamo

An extremely lightweight collection of classes for implementing a state based decoupled controller architecture for Android applications.

A major benefit of this is that it also keeps asynchronous code away from the Android lifecycle.

dynamo droid

Motivation

The majority of Android apps have no standard approach to architecture. Its very rare to come across an apps codebase that says "I have used approach X listed here". Most apps I have found fall into the below camps

  • The "God-Activity" (or Fragment) that does everything and has to balance asynchronous code with the Android lifecycle.
  • A roll your own approach which still does not address all the issues associated with mixing asynchronous code and lifecycle events, which causes it to break down in some common use-cases.

There are some great libraries out there, its just that I have not come across one which is

  1. Well documented
  2. Easy to jump into without a large ramp-up time
  3. Solves the issues that come from the "default" approaches (see the projects Wiki for more here)

Similarly, there are many great blog posts at present which cover a subset of an MV* (i.e. MVP) approach but most of the ones I have read leave me with as many questions as I have answers, do not form a complete road-map and sometimes introduce unneeded complexities or limitations. This is my attempt to create something that is a:

  1. Relatively accessible for those newer to the Android world
  2. Lightweight and easy to understand
  3. Explains how it solves common issues and provides examples for these cases.
  4. Easy to write automated tests against

Blog Posts

The motivation for this lib is further outlined in two of my blog posts:

Wiki

The Wiki for this repo contains documentation about how to use this library. Check it out.

Questions / Feedback

Admittedly, while trying to create a clear guide for people here I have probably created more confusion than you had to start with. If you have any questions, suggestions, abuse, lavish amounts of money or anything else you want to send my way please do. Use the Issues in this repo, twitter, the comments in my blog, the reddit posts, anything! At the end of the day I want to help you learn and learn myself so please feel free. Plus, if you think I have not explained something enough or have the wrong idea about something I want to hear it!

Download

jcenter/mvn coming after this short intermission

For now can just grab the latest release i know i know

Or to play with via Maven Local by cloning the repo and running ./gradlew pTML and then adding mavenLocal() to your resositories{...} and compile 'couk.doridori.dynamo:dynamo:1.0.0'

License

Copyright 2014 Dorian Cussen

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

dynamo's People

Contributors

doridori avatar mpp-doric avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dynamo's Issues

F.A.Q

create F.A.Q with questions people are asking through issues and add to wiki.

onState called several times after switch state

I call newState() from one of my states, but the onState callback is called twice on my fragment.
Also, on screen rotation, the onState callback is called several time too.

Steps tp reproduce:

  • Current state = RegisterPendingState
  • Call register method from the Fragment with params which will fail the validations (empty email)
  • RegisterPendingState will create new state -> RegisterLoadingState
  • RegisterLoadingState will validate to data in enteringState and will create a new state (RegisterFailedState) in case of an error.
  • Finally, the onState(RegisterFailedState) will be called twice in the AuthFragment

My Dynamo:

package com.weshopit.android.dynamo;

import android.support.annotation.StringRes;

import com.weshopit.android.R;
import com.weshopit.android.data.DataSourceImpl;
import com.weshopit.android.data.callbacks.LoginListener;
import com.weshopit.android.data.callbacks.RegisterListener;

import couk.doridori.dynamo.Dynamo;
import couk.doridori.dynamo.StateMachine;

public class AuthDynamo extends Dynamo<AuthDynamo.AuthState> {


    // ======================================================================================
    // CONSTRUCTOR
    // ======================================================================================
    public AuthDynamo(){
        newState(new LoginPendingState());
    }

    // ======================================================================================
    // METHODS
    // ======================================================================================

    public void showLogin() {
        getStateMachine().getCurrentState().showLogin();
    }
    public void showRegistration() {
        getStateMachine().getCurrentState().showRegistration();
    }

    public void login(String email, String password) {
        getStateMachine().getCurrentState().login(email, password);
    }

    public void register(String email, String name, String password, String passwordConfirmation) {
        getStateMachine().getCurrentState().register(email, name, password, passwordConfirmation);
    }

    public void errorAcknowledged() {
        getStateMachine().getCurrentState().errorAcknowledged();
    }

    // ======================================================================================
    // STATES
    // ======================================================================================

    protected abstract class AuthState extends StateMachine.State{

        protected String className = getClass().getName();

        public void login(String email, String password){
            throw new IllegalStateException("State " + className + " cannot login");
        }

        public void register(String email, String name, String password, String passwordConfirmation){
            throw new IllegalStateException("State " + className + " cannot register");
        }

        public void errorAcknowledged(){
            throw new IllegalStateException("State " + className + " cannot acknowledge error");
        }

        public void showLogin() {
            newState(new LoginPendingState());
        }

        public void showRegistration() {
            newState(new RegisterPendingState());
        }

        /**
         * Visitor pattern
         */
        public abstract void accept(AuthVisitor visitor);
    }

    public class LoginPendingState extends AuthState {

        @Override
        public void login(String email, String password) {
            newState(new LoginLoadingState(email, password));
        }

        @Override
        public void accept(AuthVisitor visitor){
            visitor.onState(this);
        }
    }

    public class LoginLoadingState extends AuthState {

        private final String mEmail;
        private final String mPassword;

        public LoginLoadingState(String email, String password){
            mEmail = email;
            mPassword = password;
        }

        @Override
        public void enteringState() {

            int error = 0;

            if(mEmail.isEmpty()) {
                error = R.string.error_empty_email;
            }

            if(mPassword.isEmpty()) {
                error = R.string.error_empty_password;
            }

            if(error != 0){
                newState(new LoginFailedState(error));
                return;
            }

            DataSourceImpl.getInstance().login(mEmail, mPassword, new LoginListener() {
                @Override
                public void onLoginSucceeded(String access_token, String name, String email, int expires_in) {
                    newState(new LoginSuccessState());
                }

                @Override
                public void onLoginFailed(String error) {
                    newState(new LoginFailedState(error));
                }
            });
        }

        @Override
        public void accept(AuthVisitor visitor){
            visitor.onState(this);
        }
    }

    public class LoginSuccessState extends AuthState {

        @Override
        public void accept(AuthVisitor visitor){
            visitor.onState(this);
        }
    }

    public class LoginFailedState extends AuthState {

        String mErrorText;
        int mErrorRes;

        public LoginFailedState(String error){
            mErrorText = error;
        }

        public LoginFailedState(@StringRes int error){
            mErrorRes = error;
        }

        public String getErrorText(){
            return mErrorText;
        }

        public int getErrorRes(){
            return mErrorRes;
        }

        @Override
        public void errorAcknowledged() {
            newState(new LoginPendingState());
        }

        @Override
        public void accept(AuthVisitor visitor){
            visitor.onState(this);
        }
    }

    public class RegisterPendingState extends AuthState {

        @Override
        public void register(String email, String name, String password, String passwordConfirmation) {
            newState(new RegisterLoadingState(email, name, password, passwordConfirmation));
        }

        @Override
        public void accept(AuthVisitor visitor){
            visitor.onState(this);
        }
    }

    public class RegisterLoadingState extends AuthState {

        private final String mEmail;
        private final String mName;
        private final String mPassword;
        private final String mPasswordConfirmation;

        public RegisterLoadingState(String email, String name, String password, String passwordConfirmation){
            mEmail = email;
            mName = name;
            mPassword = password;
            mPasswordConfirmation = passwordConfirmation;
        }

        @Override
        public void enteringState() {

            int error = 0;

            if(mEmail.isEmpty()) {
                error = R.string.error_empty_email;
            }

            if(mPassword.isEmpty()) {
                error = R.string.error_empty_password;
            }

            if(mName.isEmpty()) {
                error = R.string.error_empty_name;
            }

            if(!mPassword.equals(mPasswordConfirmation)) {
                error = R.string.error_password_not_match;
            }

            if(error != 0){
                newState(new RegisterFailedState(error));
                return;
            }

            // Perform registration
            DataSourceImpl.getInstance().register(mEmail, mName, mPassword, new RegisterListener() {
                @Override
                public void onRegistrationsSucceeded(String email, String password) {
                    newState(new RegisterSuccessState());
                }

                @Override
                public void onRegistrationFailed(String error) {
                    newState(new RegisterFailedState(error));
                }
            });
        }

        @Override
        public void accept(AuthVisitor visitor){
            visitor.onState(this);
        }
    }

    public class RegisterSuccessState extends AuthState {

        @Override
        public void accept(AuthVisitor visitor){
            visitor.onState(this);
        }
    }

    public class RegisterFailedState extends AuthState {

        String mErrorText;
        int mErrorRes;

        public RegisterFailedState(String error){
            mErrorText = error;
        }

        public RegisterFailedState(@StringRes int error){
            mErrorRes = error;
        }

        public String getErrorText(){
            return mErrorText;
        }

        public int getErrorRes(){
            return mErrorRes;
        }

        @Override
        public void errorAcknowledged() {
            newState(new RegisterPendingState());
        }

        @Override
        public void accept(AuthVisitor visitor){
            visitor.onState(this);
        }
    }

    //======================================================================================
    // VISITOR
    //======================================================================================

    public interface AuthVisitor {
        void onState(LoginPendingState state);
        void onState(LoginLoadingState state);
        void onState(LoginSuccessState state);
        void onState(LoginFailedState state);
        void onState(RegisterPendingState state);
        void onState(RegisterLoadingState state);
        void onState(RegisterSuccessState state);
        void onState(RegisterFailedState state);
    }

    public void visitCurrentState(AuthVisitor visitor){
        getStateMachine().getCurrentState().accept(visitor);
    }
}

My Fragment:

package com.weshopit.android.presentation.fragments;

import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;

import com.weshopit.android.R;
import com.weshopit.android.dynamo.AuthDynamo;
import com.weshopit.android.dynamo.DynamoManager;
import com.weshopit.android.presentation.activities.AuthActivity;
import com.weshopit.android.presentation.core.BaseFragment;
import com.weshopit.android.presentation.customViews.EmailHistoryAutoComplete;
import com.weshopit.android.presentation.utils.Navigator;

import java.util.List;
import java.util.Observable;
import java.util.Observer;

import butterknife.ButterKnife;
import butterknife.InjectView;
import butterknife.InjectViews;
import butterknife.OnClick;

public class AuthFragment extends BaseFragment implements
        Observer, AuthDynamo.AuthVisitor {

    @InjectView(R.id.fragment_auth_et_email)
    EmailHistoryAutoComplete mEmailEt;

    @InjectView(R.id.fragment_auth_et_name)
    EditText mNameEt;

    @InjectView(R.id.fragment_auth_et_pass)
    EditText mPassEt;

    @InjectView(R.id.fragment_auth_pass_confirmation)
    EditText mPassConfirmationEt;

    @InjectViews({
            R.id.fragment_auth_et_name,
            R.id.fragment_auth_pass_confirmation,
            R.id.fragment_auth_btn_register,
            R.id.fragment_auth_btn_switch_to_login,
            R.id.fragment_auth_btn_skip})
    List<View> registrationViews;

    @InjectViews({
            R.id.fragment_auth_btn_login,
            R.id.fragment_auth_btn_switch_to_register,
            R.id.fragment_auth_btn_forget_pass })
    List<View> loginViews;

    private AuthActivity mActivity;
    private AuthDynamo mDynamo;

    private boolean isViewCreated = false;

    // ====================================================
    // Lifecycle
    // ====================================================
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_auth, container, false);

        ButterKnife.inject(this, rootView);

        isViewCreated = true;
        init();

        return rootView;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mActivity = (AuthActivity) getActivity();

        init();
    }


    @Override
    public void onDetach() {
        super.onDetach();
        mActivity = null;
    }

    /**
     * Initialize dynamo only after the fragemtn is attached and the view were created
     */
    private void init(){
        if(isViewCreated && mActivity != null){
            mDynamo = DynamoManager.getInstance().getAuthDynamo();
            mDynamo.addObserver(this);
            mDynamo.visitCurrentState(this);
        }
    }

    public void onDisplay(){
        mEmailEt.requestFocus();
    }

    // ====================================================
    // Public methods
    // ====================================================
    public void showLogin() {
        mDynamo.showLogin();
    }

    public void showRegistration() {
        mDynamo.showRegistration();
    }

    // ====================================================
    // UI events
    // ====================================================

    @OnClick(R.id.fragment_auth_btn_switch_to_register)
    public void switchToRegistration() {
        // Switch from login to register and vice versa
        mDynamo.showRegistration();
    }
    @OnClick(R.id.fragment_auth_btn_switch_to_login)
    public void switchToLogin() {
        // Switch from login to register and vice versa
        mDynamo.showLogin();
    }

    @OnClick(R.id.fragment_auth_btn_skip)
    public void skipLogin() {
        mActivity.showSkipAuthApproval();
    }

    @OnClick(R.id.fragment_auth_btn_forget_pass)
    public void forgetPassword() {
        mActivity.showForgetPassword(mEmailEt.getText().toString());
    }

    @OnClick(R.id.fragment_auth_btn_login)
    public void login() {
        mDynamo.login(mEmailEt.getText().toString(), mPassEt.getText().toString());
    }

    @OnClick(R.id.fragment_auth_btn_register)
    public void register() {
        mDynamo.register(
                mEmailEt.getText().toString(),
                mNameEt.getText().toString(),
                mPassEt.getText().toString(),
                mPassConfirmationEt.getText().toString()
        );
    }

    //======================================================================================
    // Dynamo States
    //======================================================================================
    // the below onState methods are where we should perform all UI transitions.

    @Override
    public void onState(AuthDynamo.LoginPendingState state) {
        ButterKnife.apply(registrationViews, HIDE);
        ButterKnife.apply(loginViews, SHOW);
    }

    @Override
    public void onState(AuthDynamo.LoginLoadingState state) {
        if(mActivity != null){
            mActivity.showProgress();
        }
    }

    @Override
    public void onState(AuthDynamo.LoginSuccessState state) {
        if(mActivity != null){
            Navigator.toHome(mActivity);
            DynamoManager.getInstance().mAuthDynamoHolder.clearAll();
        }
    }

    @Override
    public void onState(AuthDynamo.LoginFailedState state) {
        if(mActivity != null) {
            mActivity.hideProgress();

            String errorText = state.getErrorText();
            if(errorText == null){
                errorText = getString(state.getErrorRes());
            }

            mActivity.showError(errorText, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    mDynamo.errorAcknowledged();
                }
            });
        }
    }

    @Override
    public void onState(AuthDynamo.RegisterPendingState state) {
        ButterKnife.apply(registrationViews, SHOW);
        ButterKnife.apply(loginViews, HIDE);
    }

    @Override
    public void onState(AuthDynamo.RegisterLoadingState state) {
        if(mActivity != null){
            mActivity.showProgress();
        }
    }

    @Override
    public void onState(AuthDynamo.RegisterSuccessState state) {
        if(mActivity != null){
            Navigator.toHome(mActivity);
            DynamoManager.getInstance().mAuthDynamoHolder.clearAll();
        }
    }

    @Override
    public void onState(AuthDynamo.RegisterFailedState state) {
        if(mActivity != null) {
            mActivity.hideProgress();

            String error = state.getErrorText();
            if(error == null){
                error = getString(state.getErrorRes());
            }

            mActivity.showError(error, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    mDynamo.errorAcknowledged();
                }
            });
        }
    }

    //======================================================================================
    // Observe controller changes
    //======================================================================================

    @Override
    public void update(Observable observable, Object data){
        // visiting the current state will result in one of the onState() methods in this class to be called
        mDynamo.visitCurrentState(this);
    }

    // ====================================================
    // Internal methods
    // ====================================================
    static final ButterKnife.Action<View> SHOW = new ButterKnife.Action<View>() {
        @Override public void apply(View view, int index) {
            view.setVisibility(View.VISIBLE);
        }
    };
    static final ButterKnife.Action<View> HIDE = new ButterKnife.Action<View>() {
        @Override public void apply(View view, int index) {
            view.setVisibility(View.GONE);
        }
    };
}

Layout:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity"
    android:background="#ffffff">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/fragment_auth_btn_switch_to_login"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_alignParentEnd="true"
            android:text="@string/login"
            />

        <Button
            android:id="@+id/fragment_auth_btn_switch_to_register"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_alignParentEnd="true"
            android:text="@string/register"
            />

        <Button
            android:id="@+id/fragment_auth_btn_forget_pass"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:text="@string/forget_password"
            />

        <Button
            android:id="@+id/fragment_auth_btn_skip"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:text="@string/skip_auth" />

    </RelativeLayout>

    <com.weshopit.android.presentation.customViews.EmailHistoryAutoComplete
        android:id="@+id/fragment_auth_et_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textEmailAddress"
        android:padding="10dp"
        android:hint="@string/email" />

    <EditText
        android:id="@+id/fragment_auth_et_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:hint="@string/name"
        />

    <EditText
        android:id="@+id/fragment_auth_et_pass"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textPassword"
        android:layout_marginTop="-2dp"
        android:padding="10dp"
        android:hint="@string/password"
        />

    <EditText
        android:id="@+id/fragment_auth_pass_confirmation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textPassword"
        android:layout_marginTop="-2dp"
        android:padding="10dp"
        android:hint="@string/confirmation"
        />

    <Button
        android:id="@+id/fragment_auth_btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:layout_margin="4dp"
        android:text="@string/sign_in"
        />

    <Button
        android:id="@+id/fragment_auth_btn_register"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:layout_margin="4dp"
        android:text="@string/register"
        />

</LinearLayout>

A Dynamo that extends Rx.Observable

Need to have a think about if this would add any benefit. Interested in hooking up with RxAndroid to remove the boilerplate of adding and removing observers based upon the views lifecycle. One for another day!

Change focus of project

I am finding that Dynamo is nice to use as the state machine component of a wider arch like https://github.com/doridori/Pilot. This can be bundled inside a presenter and used to represent the state which can be easily queried by a lifecycle-affected view.

Should change the focus of project as something thats useful inside presenters (or state machine in general). Changes involve

  • Deprecating part of wiki. Lots of wiki is talking about dynamo as a complete arch in itself. Point to pilot where needed.
  • Creating an extendable LCE (loading/content/error) state machine as this is most common use case. (maybe simpler for a strict LCE not to have a state machine behind the scenes anyhow)
  • DynamoManager (really a Map) should prob be optional extra as only used when you have a presenter hosting child presenters for child viewa

wiki spelling/grammer

Not sure if this interests you, but here are some very minor misspellings I noticed in the wiki:

page 4. You extend=s= this for each
page 5. It is a singleton tha=n= handles
page 8. It='=s great and I recommend =it=
page 8. Activit=y=s, not sure if Activities is more appropriate here
page 4. "5. Design Scenarios (Recipes)" link is broken

Annotation driven to remove boilerplate Dynamo hookups

There is a small amount of boilerplate for observing changes inside view components. Would be interesting to see if this can be made easier using annotations.

Wary as don't want to turn it into a magic box which has a whole load of stuff going on thats not obvious.

Depending on the use-cases part of the Dyanmo / Dynamo holder hookup is related to hooking into the lifecycle (and part is UUID storing). This also could be annotation driven OR could have the non-ui fragment approach to hook into lifecycle ala Glide

How to handle callback from the DB layer

In my app I have 3 main layers:

  • Presentation layer - which includes: activities, fragments, dialogs etc..
  • Dynamo layer - which has all dynamos and the dynamo manager
  • DB layer - which communicates with different sources

I have a list with various items, and I want to delete one of them.
Currently, the flow is:

  • The fragment calls deleteItem() method of the dynamo
  • The dynamo calls deleteItem() of the current state "PendingState"
  • The state calls deleteItem() of the DB layer and waits for the success callback
  • When the success callback is called. I switch to another state ("DeleteItemState") which its only purpose is to make the fragment delete the item.
  • In the fragment, when the onState of the "DeleteItemState" called, I remove the item from the adapter and calls the dynamo to switch again to the PendingState

My solution works, but I guess you have better idea of how to handle callbacks from the DB layer without dedicated state.

Thanks a lot!

More Examples needed

  • Saving state on process death
  • Removing Dynamo on lifecycle based timer. Useful for memory footprint and or security requirements
  • WeakRef based DynamoHolder
  • Dynamos in listView
  • Multiple views on same Dynamo
  • Dynamo hooked up to View for Fixed, Variable and UUID (instance based) meta-key data
  • Example application & blog post

Should add some tests in for these also

Some thouhts/questions on Dynamo

First off, the wiki (first 5 pages) and blog posts were fantastic reads. Definitely the best I've read on this subject. I will definitely be returning to this material to think more about what you're proposed.

After reading, I had a few questions:

On page 5, you say that:

Its important that each state should be able to expose its own interface, for example ComputationFinishedState has a getResult() method. This is why we have used the Visitor pattern as it allows us to do this easily.

  1. I had a little trouble seeing how the Visitor pattern makes it easier for each State to expose its own interface. Why can't a simple callback do this as well as a visitor?
  2. I want to make sure I've understood how the objects in this architecture "hang together," so I want to try to apply your approach to a scenario that didn't seem to be explicitly covered in your wiki. Suppose you want to do something when an Activity's onStop() method is called. Suppose further that the code you want to execute has a lot of business logic in it. On your approach, that business logic wouldn't live in the Activity. Instead, you'd call a method on a dynamo which would then call a method on a State and the State would handle the business logic. Does that sound like the dynamo-way of handling that scenario?

(suggestion) Activity.isFinishing() in the docs

Hi,

In Design scenarios, Part 3, 3. Screen by screen, I think using Activity.isFinishing() to check if an activity is actually finishing or if a configuration change is happening would be a better approach. Please tell me if I missed some usecase where it would not work.

Thanks for this project by the way, the documentation is really good, looks promising!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.