Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[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

Google Developers Group Lviv

October 14, 2017
Tweet

More Decks by Google Developers Group Lviv

Other Decks in Technology

Transcript

  1. 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(); } }); }
  2. 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(); } }); }
  3. 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(); } }); }
  4. 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(); } }); }
  5. 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(); } }); }
  6. 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(); } }); }
  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. Potential problems • Dependency management • Memory leaks • Crash

    • Object Status • Code coupling • Non testable • Not reusable
  10. The best architecture is the one that YOU can easily

    understand, explain to others and rapidly iterate over new features with minimal code refactoring.
  11. 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
  12. 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
  13. ProfileDao View ViewModel Profiles Repository Profile NetworkDataSource SQLite Very fast

    API Room LiveData<List<Profile> LiveData<Profile[]> Observe Observe LifecycleOwner
  14. @Entity public class Profile { @PrimaryKey @NonNull private String name;

    private String imageUrl; private Integer age; private String location; }
  15. @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(); }
  16. @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(); }
  17. @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(); }
  18. @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(); }
  19. @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(); }
  20. @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();
  21. @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();
  22. @Module public class AppModule { @Provides @Singleton AppDatabase providesDatabase(Context context)

    { return Room.inMemoryDatabaseBuilder(context, AppDatabase.class).build(); } } Our example - Adding Dagger2
  23. 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.
  24. 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 } });
  25. 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
  26. 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(); }
  27. 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());
  28. 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());
  29. 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());
  30. 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
  31. 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); }
  32. 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); }
  33. 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); }
  34. 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; } }
  35. 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; } }
  36. 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; } }
  37. Get Profiles UserDao View ViewModel Users Repository UserNetworkDataSource SQLite Very

    fast API Room LiveData<List<User> LiveData<User[]> Observe Observe LifecycleOwner Load Profiles
  38. 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>
  39. 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
  40. @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 }
  41. ProfileRepository profileRepository; private LiveData<List<Profile>> profiles; MainViewModel(ProfileRepository profileRepository) { this.profileRepository =

    profileRepository; profiles = profileRepository.getProfiles(); } public LiveData<List<Profile>> getProfiles() { return profiles; }
  42. @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()); }
  43. public LiveData<List<Profile>> getProfiles() { initializeData(); return db.profileDao().getProfiles(); } private void

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

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