Android
Architecture
Components

About me

Alejandro Vidal Rodriguez
Director of Engineer @TribalScale

www.tribalscale.com

we are HIRING!

Agenda

  • History

  • Setup

  • LifeCycle

  • LiveData

  • ViewModel

  • Error handling

  • States

  • Room

  • Demo project

What are we building?

an e-commerce app

Repository


git clone git@github.com:goofyahead/AndroidArchComponentsTalk.git

Once upon a time 

We changed to

Then...

and..

which leads to

what are we looking for

Architecture components

Setup

dependencies {
    // ViewModel and LiveData
    implementation "android.arch.lifecycle:extensions:1.1.1"
    // alternatively, just ViewModel
    implementation "android.arch.lifecycle:viewmodel:1.1.1"
    // alternatively, just LiveData
    implementation "android.arch.lifecycle:livedata:1.1.1"

    annotationProcessor "android.arch.lifecycle:compiler:1.1.1"

    // Room (use 1.1.0-beta2 for latest beta)
    implementation "android.arch.persistence.room:runtime:1.0.0"
    annotationProcessor "android.arch.persistence.room:compiler:1.0.0"

    // Paging
    implementation "android.arch.paging:runtime:1.0.0-beta1"

    // Test helpers for LiveData
    testImplementation "android.arch.core:core-testing:1.1.1"

    // Test helpers for Room
    testImplementation "android.arch.persistence.room:testing:1.0.0"
}

Handling LifeCycle

LifeCycleOwner

public class MyActivity extends Activity implements LifecycleOwner {
    private LifecycleRegistry mLifecycleRegistry;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mLifecycleRegistry = new LifecycleRegistry(this);
        mLifecycleRegistry.markState(Lifecycle.State.CREATED);
    }

    @Override
    public void onStart() {
        super.onStart();
        mLifecycleRegistry.markState(Lifecycle.State.STARTED);
    }

    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return mLifecycleRegistry;
    }
}

LifeCycleOwner in Fragment

void performActivityCreated(Bundle savedInstanceState) {
        ...
        mState = ACTIVITY_CREATED;
}

void performStart() {
        ...
        mState = STARTED;
        mCalled = false;
        ...
        mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
}

void performResume() {
        ...
        mState = RESUMED;
        mCalled = false;
        onResume();
        ...
        mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
}

LifeCycleObserver

class MyLocationListener implements LifecycleObserver {
    private boolean enabled = false;
    public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
       ...
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    void start() {
        if (enabled) {
           // connect
        }
    }

    public void enable() {
        enabled = true;
        if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
            // connect if not connected
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    void stop() {
        // disconnect if connected
    }
}

LifeCycleObserver in LiveData

class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
        @NonNull final LifecycleOwner mOwner;
        ...

        @Override
        boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            activeStateChanged(shouldBeActive());
        }

        @Override
        boolean isAttachedTo(LifecycleOwner owner) {
            return mOwner == owner;
        }

        @Override
        void detachObserver() {
            mOwner.getLifecycle().removeObserver(this);
        }
}

PROBLEM SOLVED

LiveData<T>

is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware.

LiveData<T>

public class NameViewModel extends ViewModel {

// Create a LiveData with a String
private MutableLiveData<String> mCurrentName;

    public MutableLiveData<String> getCurrentName() {
        if (mCurrentName == null) {
            mCurrentName = new MutableLiveData<String>();
        }
        return mCurrentName;
    }

// Rest of the ViewModel...
}

How do I use it?

public class NameActivity extends AppCompatActivity {

    private NameViewModel mModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Other code to setup the activity...

        // Get the ViewModel.
        mModel = ViewModelProviders.of(this).get(NameViewModel.class);

        // Create the observer which updates the UI.
        final Observer<String> nameObserver = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String newName) {
                // Update the UI, in this case, a TextView.
                mNameTextView.setText(newName);
            }
        };

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        mModel.getCurrentName().observe(this, nameObserver);
    }
}

What's this?

 // Get the ViewModel.
 mModel = ViewModelProviders.of(this).get(NameViewModel.class);

the same old issue

void onApiCallResult(Product product) {
    textView.setText(product.getName()); // What if the textview is not there?
}

with ViewModelProvider

Rx vs LiveData

LiveData is a holder and not a stream.
So we have a reference to the last value, and observer immediately receives the last value when they start to observe liveData.

Operators

map

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});

switchMap

private LiveData<User> getUser(String id) {
  ...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
mObservableProducts = new MediatorLiveData<>();
// set by default null, until we get data from the database.
mObservableProducts.setValue(null);

LiveData<List<ProductEntity>> products = repository.getProducts();

// observe the changes of the products from the database and forward them
mObservableProducts.addSource(products, mObservableProducts::setValue);

Mediator

Show it to me 

ViewModel

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way.

ViewModel

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<Users>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

NO VIEW REFERENCE!

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        users = new MutableLiveData<List<Users>>();
        loadUsers();
        return users;
    }
}

Shared ViewModel

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}

public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, item ->Update the UI );
    }
}

what we put on the ViewModel?

Error handling

Error handling

class SearchStateModel {
  Throwable error; // if not null, an error has occurred
  boolean loading; // if true loading data is in progress
  List<Product> result; // if not null this is the result of the search
  boolean SearchNotStartedYet; // if true, we have the search not started yet
}

class SearchViewModel extends ViewModel<SearchStateModel> {

  LiveData<SearchStateModel> state;

  public LiveData<SearchStateModel> load(){
    state = new PersonsModel(true, null, null) ); // Displays a ProgressBar
    
    backend.loadPersons(new Callback(){
      public void onSuccess(List<Person> persons){
        state = new PersonsModel(false, persons, null) ); // Displays a list of Persons
      }

      public void onError(Throwable error){
        state = new PersonsModel(false, null, error) ); // Displays a error message
      }
    });

    return state;
  }
}

ViewModel as a state

http://hannesdorfmann.com/android/mosby3-mvi-1

Room & data storage

Room (Entity)

@Entity
public class User {
    @PrimaryKey
    private int uid;

    @ColumnInfo(name = "first_name")
    private String firstName;

    @ColumnInfo(name = "last_name")
    private String lastName;

    // Getters and setters are ignored for brevity,
    // but they're required for Room to work.
}

Room (Dao)

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
           + "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

Room (database)

@Database(entities = {Word.class}, version = 1)
public abstract class WordRoomDatabase extends RoomDatabase {

   public abstract WordDao wordDao();
   private static WordRoomDatabase INSTANCE;

   static WordRoomDatabase getDatabase(final Context context) {
       if (INSTANCE == null) {
           synchronized (WordRoomDatabase.class) {
               if (INSTANCE == null) {
                   INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                           WordRoomDatabase.class, "word_database")
                           .build();                
                   
               }
           }
       }
       return INSTANCE;
   }

}

Room (return types)

@Query("SELECT * from word_table ORDER BY word ASC")
LiveData<List<Word>> getAllWords();

@Query("SELECT * from word_table ORDER BY word ASC")
List<Word> getAllWords();

@Query("SELECT * from word_table ORDER BY word ASC")
Flowable <List<Word>> getAllWords();

@Query("SELECT * from word_table ORDER BY word ASC")
Maybe<List<Word>> getAllWords();

@Query("SELECT * from word_table ORDER BY word ASC")
Single<List<Word>> getAllWords();

Show me the code!

References

Arh doc:
https://developer.android.com/topic/libraries/architecture/guide.html

Google sample repos:
https://github.com/googlesamples/android-architecture-components

MVC vs MVP vs MVVM 
https://academy.realm.io/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/

MVVM & Rx
https://proandroiddev.com/mvvm-architecture-using-livedata-rxjava-and-new-dagger-android-injection-639837b1eb6c

ROOM
https://codelabs.developers.google.com/codelabs/android-room-with-a-view/#6

MVI
http://hannesdorfmann.com/android/mosby3-mvi-1

Questions?

contact

arodriguez@tribalscale.com

https://medium.com/@goofyahead

Architecture components

By Alejandro Vidal Rodriguez

Architecture components

  • 29