[Yonatan Levin] Architecture Components. The one that got away

[Yonatan Levin] Architecture Components. The one that got away

Presentation from GDG DevFest Ukraine 2017 - the biggest community-driven Google tech conference in the CEE.

Learn more at: https://devfest.gdg.org.ua

3a6de6bc902de7f75c0e753b3202ed52?s=128

Google Developers Group Lviv

October 14, 2017
Tweet

Transcript

  1. Yonatan V.Levin levin.yonatan parahall Google Developer Expert

  2. >100 Cities > 30M users Ruby, Go, Python, Microservices Ooooops...

  3. Making genetic tests accessible

  4. > 2000 members Largest Android Active Community

  5. Ask questions. It’s for you.

  6. None
  7. public boolean onClick(View view) { OrderApi client = mRetrofitManager.createRetrofitClient(OrderApi.class); client.getIndex(new

    Callback<List<Order>>() { @Override public void onResponse(Call<List<Order>> call, Response<List<Order>> response) { mDB.update(response.body()); Fragment orderListFragment = new OrderListFragment(); getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, orderListFragment, OrderListFragment.TAG) .commit(); } @Override public void onFailure(Call<List<Order>> call, Throwable t) { declineDialog.show(); } }); }
  8. public boolean onClick(View view) { OrderApi client = mRetrofitManager.createRetrofitClient(OrderApi.class); client.getIndex(new

    Callback<List<Order>>() { @Override public void onResponse(Call<List<Order>> call, Response<List<Order>> response) { mDB.update(response.body()); Fragment orderListFragment = new OrderListFragment(); getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, orderListFragment, OrderListFragment.TAG) .commit(); } @Override public void onFailure(Call<List<Order>> call, Throwable t) { declineDialog.show(); } }); }
  9. public boolean onClick(View view) { OrderApi client = mRetrofitManager.createRetrofitClient(OrderApi.class); client.getIndex(new

    Callback<List<Order>>() { @Override public void onResponse(Call<List<Order>> call, Response<List<Order>> response) { mDB.update(response.body()); Fragment orderListFragment = new OrderListFragment(); getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, orderListFragment, OrderListFragment.TAG) .commit(); } @Override public void onFailure(Call<List<Order>> call, Throwable t) { declineDialog.show(); } }); }
  10. public boolean onClick(View view) { OrderApi client = mRetrofitManager.createRetrofitClient(OrderApi.class); client.getIndex(new

    Callback<List<Order>>() { @Override public void onResponse(Call<List<Order>> call, Response<List<Order>> response) { mDB.update(response.body()); Fragment orderListFragment = new OrderListFragment(); getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, orderListFragment, OrderListFragment.TAG) .commit(); } @Override public void onFailure(Call<List<Order>> call, Throwable t) { declineDialog.show(); } }); }
  11. public boolean onClick(View view) { OrderApi client = mRetrofitManager.createRetrofitClient(OrderApi.class); client.getIndex(new

    Callback<List<Order>>() { @Override public void onResponse(Call<List<Order>> call, Response<List<Order>> response) { mDB.update(response.body()); Fragment orderListFragment = new OrderListFragment(); getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, orderListFragment, OrderListFragment.TAG) .commit(); } @Override public void onFailure(Call<List<Order>> call, Throwable t) { declineDialog.show(); } }); }
  12. public boolean onClick(View view) { OrderApi client = mRetrofitManager.createRetrofitClient(OrderApi.class); client.getIndex(new

    Callback<List<Order>>() { @Override public void onResponse(Call<List<Order>> call, Response<List<Order>> response) { mDB.update(response.body()); Fragment orderListFragment = new OrderListFragment(); getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, orderListFragment, OrderListFragment.TAG) .commit(); } @Override public void onFailure(Call<List<Order>> call, Throwable t) { declineDialog.show(); } }); }
  13. public boolean onClick(View view) { OrderApi client = mRetrofitManager.createRetrofitClient(OrderApi.class); client.getIndex(new

    Callback<List<Order>>() { @Override public void onResponse(Call<List<Order>> call, Response<List<Order>> response) { mDB.update(response.body()); Fragment orderListFragment = new OrderListFragment(); getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, orderListFragment, OrderListFragment.TAG) .commit(); } @Override public void onFailure(Call<List<Order>> call, Throwable t) { declineDialog.show(); } }); }
  14. public boolean onClick(View view) { OrderApi client = mRetrofitManager.createRetrofitClient(OrderApi.class); client.getIndex(new

    Callback<List<Order>>() { @Override public void onResponse(Call<List<Order>> call, Response<List<Order>> response) { mDB.update(response.body()); Fragment orderListFragment = new OrderListFragment(); getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, orderListFragment, OrderListFragment.TAG) .commit(); } @Override public void onFailure(Call<List<Order>> call, Throwable t) { declineDialog.show(); } }); }
  15. Events Timeline Activity Created Network call in progress ... Activity

    Destroyed onResponse() called
  16. Potential problems • Dependency management • Memory leaks • Crash

    • Object Status • Code coupling • Non testable • Not reusable
  17. None
  18. Android Architecture Components

  19. Together or Separately.

  20. None
  21. The best architecture is the one that YOU can easily

    understand, explain to others and rapidly iterate over new features with minimal code refactoring.
  22. None
  23. “Act Locally, Sync Globally” Yigit Boyar

  24. Sync Users Get Profiles ProfileDao Select * from profile Get

    Profiles I’m ready View ViewModel Profiles Repository Profile Network DataSource SQLite Very fast API GET http://.../profiles
  25. List<Profiles> ProfileDao Profiles List<Profile> View ViewModel Profiles Repository Profile Network

    DataSource SQLite Very fast API Show Profiles
  26. Show Profiles Response profiles.json List<Profile> List<Profile> ProfileDao Update Profiles table

    List<Profile> View ViewModel Profile Repository Profile Network DataSource SQLite Very fast API
  27. ProfileDao View ViewModel Profiles Repository Profile NetworkDataSource SQLite Very fast

    API Room LiveData<List<Profile> LiveData<Profile[]> Observe Observe LifecycleOwner
  28. Room Persistence Library

  29. None
  30. @Entity public class Profile { @PrimaryKey @NonNull private String name;

    private String imageUrl; private Integer age; private String location; }
  31. @Dao public interface ProfileDao { @Query("SELECT * FROM Profile") LiveData<List<Profile>>

    getProfiles(); @Insert(onConflict = OnConflictStrategy.REPLACE) void bulkInsert(Profile... profile); @Query("DELETE FROM Profile") void deleteProfiles(); @Query("SELECT COUNT(NAME) FROM Profile") int countProfiles(); }
  32. @Dao public interface ProfileDao { @Query("SELECT * FROM Profile") LiveData<List<Profile>>

    getProfiles(); @Insert(onConflict = OnConflictStrategy.REPLACE) void bulkInsert(Profile... profile); @Query("DELETE FROM Profile") void deleteProfiles(); @Query("SELECT COUNT(NAME) FROM Profile") int countProfiles(); }
  33. @Dao public interface ProfileDao { @Query("SELECT * FROM Profile") LiveData<List<Profile>>

    getProfiles(); @Insert(onConflict = OnConflictStrategy.REPLACE) void bulkInsert(Profile... profile); @Query("DELETE FROM Profile") void deleteProfiles(); @Query("SELECT COUNT(NAME) FROM Profile") int countProfiles(); }
  34. @Dao public interface ProfileDao { @Query("SELECT * FROM Profile") LiveData<List<Profile>>

    getProfiles(); @Insert(onConflict = OnConflictStrategy.REPLACE) void bulkInsert(Profile... profile); @Query("DELETE FROM Profile") void deleteProfiles(); @Query("SELECT COUNT(NAME) FROM Profile") int countProfiles(); }
  35. @Dao public interface ProfileDao { @Query("SELECT * FROM Profile") LiveData<List<Profile>>

    getProfiles(); @Insert(onConflict = OnConflictStrategy.REPLACE) void bulkInsert(Profile... profile); @Query("DELETE FROM Profile") void deleteProfiles(); @Query("SELECT COUNT(NAME) FROM Profile") int countProfiles(); }
  36. @Database(entities = { Profile.class }, version = 1) public abstract

    class AppDatabase extends RoomDatabase { public abstract ProfileDao profileDao(); } AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name").build();
  37. @Database(entities = { Profile.class }, version = 1) public abstract

    class AppDatabase extends RoomDatabase { public abstract ProfileDao profileDao(); } AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name").build();
  38. @Module public class AppModule { @Provides @Singleton AppDatabase providesDatabase(Context context)

    { return Room.inMemoryDatabaseBuilder(context, AppDatabase.class).build(); } } Our example - Adding Dagger2
  39. LiveData component

  40. LiveData what? • Gets updates to the data while the

    Lifecycle is in an active state (STARTED or RESUMED) • Is removed when the LifecycleOwner is destroyed • Gets up-to-date data when the LifecycleOwner restarts due to a configuration change or is restarted from the back stack This helps to eliminate many pathways for memory leaks, and reduces crashes by avoiding updates to stopped activities.
  41. Usage private LiveData<List<Profile>> profiles; private void subscribeToDbChanges() { profiles =

    mDb.profileDao().getProfiles(); profiles.observe(this, new Observer<List<Profile>>() { @Override public void onChanged(@Nullable List<Profile> profiles) { //update UI } });
  42. Usage private LiveData<List<Profile>> profiles; private void subscribeToDbChanges() { profiles =

    mDb.profileDao().getProfiles(); profiles.observe(this, new Observer<List<Profile>>() { @Override public void onChanged(@Nullable List<Profile> profiles) { //update UI } }); this LifeCycleOwner
  43. LiveData<List<Profile>> networkData = profileNetworkDataSource.getCurrentProfiles(); networkData.observeForever(new Observer<List<Profile>>() { @Override public void

    onChanged(@Nullable List<Profile> profilesFromNetwork) { //insert to DB } });
  44. LiveData<List<Profile>> networkData = profileNetworkDataSource.getCurrentProfiles(); networkData.observeForever(new Observer<List<Profile>>() { @Override public void

    onChanged(@Nullable List<Profile> profilesFromNetwork) { //insert to DB } });
  45. MutableLiveData MutableLiveData<List<Profile>> mDownloadedProfiles; public void provideLoadedProfiles(List<Profile> profiles) { mDownloadedProfiles.postValue(profiles); //OR

    mDownloadedProfiles.setValue(profiles); }
  46. MutableLiveData MutableLiveData<List<Profile>> mDownloadedProfiles; public void provideLoadedProfiles(List<Profile> profiles) { mDownloadedProfiles.postValue(profiles); //OR

    mDownloadedProfiles.setValue(profiles); }
  47. LifeCycle Component

  48. LifeCycle Abstract class Has Event and State Dispatching changes in

    States via Events to Observers
  49. None
  50. implementation "com.android.support:appcompat-v7:26.1.0" package android.support.v4.app; class SupportActivity extends Activity implements LifecycleOwner

    class Fragment implements LifecycleOwner class CustomLifeCycleOwner implements LifecycleOwner public interface LifecycleOwner { Lifecycle getLifecycle(); }
  51. LifeCycleObserver public class MyObserver implements LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void

    onResume() { } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) public void onPause() { } } aLifecycleOwner.getLifecycle().addObserver(new MyObserver());
  52. LifeCycleObserver public class MyObserver implements LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void

    onResume() { } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) public void onPause() { } } aLifecycleOwner.getLifecycle().addObserver(new MyObserver());
  53. LifeCycleObserver public class MyObserver implements LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void

    onResume() { } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) public void onPause() { } } aLifecycleOwner.getLifecycle().addObserver(new MyObserver());
  54. None
  55. None
  56. What is it? Preparing the data for the UI Stores*

    UI Data Separate out view data ownership from UI controller logic Automatically retained during configuration changes React on Events from UI
  57. None
  58. Creation private MainViewModel mainViewModel; @Override protected void onCreate(Bundle savedInstanceState) {

    //... mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class); //... subscribeUiUpdates(); } private void subscribeUiUpdates() { mainViewModel.getProfiles().observe(this, this); }
  59. Creation private MainViewModel mainViewModel; @Override protected void onCreate(Bundle savedInstanceState) {

    //... mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class); //... subscribeUiUpdates(); } private void subscribeUiUpdates() { mainViewModel.getProfiles().observe(this, this); }
  60. Creation private MainViewModel mainViewModel; @Override protected void onCreate(Bundle savedInstanceState) {

    //... mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class); //... subscribeUiUpdates(); } private void subscribeUiUpdates() { mainViewModel.getProfiles().observe(this, this); }
  61. public class MainActivityViewModel extends ViewModel { ProfileRepository profileRepository; private LiveData<List<Profile>>

    profiles; MainActivityViewModel(ProfileRepository profileRepository) { this.profileRepository = profileRepository; profiles = profileRepository.getProfiles(); } public LiveData<List<Profile>> getProfiles() { return profiles; } }
  62. public class MainActivityViewModel extends ViewModel { ProfileRepository profileRepository; private LiveData<List<Profile>>

    profiles; MainActivityViewModel(ProfileRepository profileRepository) { this.profileRepository = profileRepository; profiles = profileRepository.getProfiles(); } public LiveData<List<Profile>> getProfiles() { return profiles; } }
  63. public class MainActivityViewModel extends ViewModel { ProfileRepository profileRepository; private LiveData<List<Profile>>

    profiles; MainActivityViewModel(ProfileRepository profileRepository) { this.profileRepository = profileRepository; profiles = profileRepository.getProfiles(); } public LiveData<List<Profile>> getProfiles() { return profiles; } }
  64. Back to ...

  65. Get Profiles UserDao View ViewModel Users Repository UserNetworkDataSource SQLite Very

    fast API Room LiveData<List<User> LiveData<User[]> Observe Observe LifecycleOwner Load Profiles
  66. Update Profile table List<Profile> Response profiles.js on UserDao View ViewModel

    Users Repository UserNetworkDataSource SQLite Very fast API Room LiveData<List<User> LiveData<User[]> LifecycleOwner List<Profile> List<Profile>
  67. View ViewModel Users Repository UserNetworkDataSource Very fast API Room LiveData<List<User>

    LiveData<User[]> Observe Observe LifecycleOwner Firebase JobDispatcher Recurring sync New match, messages Firebase Push Notification
  68. https://github.com/parahall/StarWarsTinder

  69. Should I change my architecture because cool guy on stage

    said that?
  70. You have a force. Use it wisely

  71. Questions ?

  72. Yonatan V.Levin levin.yonatan parahall Google Developer Expert

  73. Q&A (Additional code)

  74. @Inject MainViewModelFactory factory; @Override protected void onCreate(Bundle savedInstanceState) { MainViewModel

    mainViewModel = ViewModelProviders.of(this, factory). get(MainViewModel.class); mainViewModel.getProfiles().observe(this, this); } @Override public void onChanged(@Nullable List<Profile> profiles) { //update UI }
  75. ProfileRepository profileRepository; private LiveData<List<Profile>> profiles; MainViewModel(ProfileRepository profileRepository) { this.profileRepository =

    profileRepository; profiles = profileRepository.getProfiles(); } public LiveData<List<Profile>> getProfiles() { return profiles; }
  76. @Inject public ProfileRepository(final AppDatabase db, ProfileNetworkDataSource dataSource) { LiveData<List<Profile>> networkData

    = dataSource.getCurrentProfiles(); networkData.observeForever(profilesFromNetwork -> new Thread(() -> { deleteOldData(); db.profileDao().bulkInsert(profilesFromNetwork); }).start()); }
  77. public LiveData<List<Profile>> getProfiles() { initializeData(); return db.profileDao().getProfiles(); } private void

    initializeData() { new Thread(() -> { if (isFetchNeeded()) { startFetchProfilesService(); } }).start(); }
  78. public LiveData<List<Profile>> getProfiles() { initializeData(); return db.profileDao().getProfiles(); } private void

    initializeData() { new Thread(() -> { if (isFetchNeeded()) { startFetchProfilesService(); } }).start(); }