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-1Room & 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-1Questions?
contact
arodriguez@tribalscale.com
https://medium.com/@goofyahead

Architecture components
By Alejandro Vidal Rodriguez
Architecture components
- 29