Slide 1

Slide 1 text

Architecture Components Constantine Mars Team Lead, Senior Developer @ DataArt Построение современной архитектуры мобильных приложений

Slide 2

Slide 2 text

Background Software Architecture Basics

Slide 3

Slide 3 text

Components: -Activities -Fragments -Services -Content Providers -Broadcast Receivers Android Has “Good Bones”

Slide 4

Slide 4 text

App-hopping OS may kill app at random time App components lifecycle is not under control Components should not depend on each other Can’t rely on data, stored in components There are Everyday Problems to Solve

Slide 5

Slide 5 text

Simply related to Activity Lifecycle

Slide 6

Slide 6 text

-Separation of concerns -Provide solid user experience -Keep UI lean and simple -Keep UI free of app logic -Drive UI from model -Use persistent model -Assign clear responsibilities for each model class Common Principles for staying in mind :)

Slide 7

Slide 7 text

Image from fernandocejas.com Separation of Concerns...

Slide 8

Slide 8 text

Image from fernandocejas.com Clean Architecture

Slide 9

Slide 9 text

-Modular app -Each class responsible for one well-defined function -Should be no god objects -The app should be testable Remember Good Architecture Goals...

Slide 10

Slide 10 text

Android Recommended Architecture

Slide 11

Slide 11 text

Android Recommended Architecture. Another View

Slide 12

Slide 12 text

Be together. not the same

Slide 13

Slide 13 text

“It is impossible to have one way of writing apps that will be the best for every scenario. That being said, this recommended architecture should be a good starting point for most use cases. If you already have a good way of writing Android apps, you don't need to change.” Be together. not the same

Slide 14

Slide 14 text

Building blocks Architecture Components

Slide 15

Slide 15 text

Purpose: Display data and pass on UI events Neither contain the UI data, nor directly manipulate data Examples: Activity, Fragment Views = UI Controllers = LifecycleOwners

Slide 16

Slide 16 text

Lifecycle = states + events

Slide 17

Slide 17 text

Implement Lifecycle since Beta AppCompatActivity, Fragment

Slide 18

Slide 18 text

ViewModel Data holder for Activity/Fragment Survives configuration changes NEVER references View / Activity / Fragment

Slide 19

Slide 19 text

ViewModel public class DetailActivityViewModel extends ViewModel { private WeatherEntry mWeather; public DetailActivityViewModel() {} public WeatherEntry getWeather() { return mWeather; } public void setWeather(WeatherEntry weatherEntry) { mWeather = weatherEntry; } }

Slide 20

Slide 20 text

ViewModel and Lifecycle

Slide 21

Slide 21 text

ViewModel and LifecycleOwner public class DetailActivity extends LifecycleActivity { DetailActivityViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... viewModel = ViewModelProviders.of(this).get(DetailActivityViewModel.class); } ...

Slide 22

Slide 22 text

Represent data needed for the UI to display An observable data holder Lifecycle aware Automatic subscription management LiveData

Slide 23

Slide 23 text

LiveData event propagation

Slide 24

Slide 24 text

LiveData sample implementation

Slide 25

Slide 25 text

Live Data public class DetailActivityViewModel extends ViewModel { private MutableLiveData mWeather; public DetailActivityViewModel() {} public MutableLiveData getWeather() { return mWeather; } public void setWeather(WeatherEntry weatherEntry) { mWeather.postValue(weatherEntry); } }

Slide 26

Slide 26 text

Live Data observing public class DetailActivity extends LifecycleActivity { DetailActivityViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... viewModel = ViewModelProviders.of(this).get(DetailActivityViewModel.class); viewModel.getWeather().observe(this, weatherEntry -> { if(weatherEntry!=null) { bindWeatherToUI(weatherEntry); } }); }

Slide 27

Slide 27 text

-Single source of truth -ViewModels simply request data from the repository -Is a mediator between the different data sources Repository Image from fernandocejas.com

Slide 28

Slide 28 text

Manages data from a remote data source, such as the internet May use REST, Cloud Remote Network Data Source

Slide 29

Slide 29 text

Manages local data stored in the database Model

Slide 30

Slide 30 text

Each class in the diagram only stores a reference to the class or classes directly "below it" and not any classes above it Layered architecture pattern

Slide 31

Slide 31 text

ORM by Google Room

Slide 32

Slide 32 text

-Less boilerplate compared to the built-in APIs -Compile-time validation of SQL queries -Data observation via LiveData, RxJava Room ORM purposes

Slide 33

Slide 33 text

-@Entity -@Dao -@Database Room annotations

Slide 34

Slide 34 text

Entity

Slide 35

Slide 35 text

@Entity declaration @Entity(tableName = "weather", indices = {@Index(value = {"date"}, unique = true)}) public class WeatherEntry { @PrimaryKey(autoGenerate = true) private int id; … }

Slide 36

Slide 36 text

@Entity constructors //Room constructor public WeatherEntry(int id, int weatherIconId, Date date, ...) { //Json constructor - ignored by Room @Ignore public WeatherEntry(int weatherIconId, Date date, // (!) Only one constructor should be exposed to Room ...

Slide 37

Slide 37 text

Dao

Slide 38

Slide 38 text

@Dao declaration @Dao public interface WeatherDao { @Query("SELECT * FROM weather") List getAll(); @Insert(onConflict = OnConflictStrategy.REPLACE) void insertAll(WeatherEntry... weatherEntries); @Delete void delete(WeatherEntry weatherEntry); }

Slide 39

Slide 39 text

Database

Slide 40

Slide 40 text

@Database declaration @Database(entities = {WeatherEntry.class}, version = 1) @TypeConverters(DateConverter.class) public abstract class AppDatabase extends RoomDatabase { public abstract WeatherDao weatherDao(); }

Slide 41

Slide 41 text

@Database - a singleton private static final String DATABASE_NAME = "weather"; private static final Object LOCK = new Object(); private static volatile AppDatabase sInstance; public static AppDatabase getInstance(Context context) { if (sInstance == null) { synchronized (LOCK) { if (sInstance == null) { sInstance = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, AppDatabase.DATABASE_NAME).build(); } }} return sInstance; }

Slide 42

Slide 42 text

Type converters class DateConverter { @TypeConverter public static Date toDate(Long timestamp) { return timestamp == null ? null : new Date(timestamp); } @TypeConverter public static Long toTimestamp(Date date) { return date == null ? null : date.getTime(); } }

Slide 43

Slide 43 text

Alternatives

Slide 44

Slide 44 text

Repository

Slide 45

Slide 45 text

Repository pattern public class WeatherRepository { public synchronized static WeatherRepository getInstance(); public synchronized void initializeData(); private void deleteOldData(); private boolean isFetchNeeded(); private void startFetchWeatherService(); }

Slide 46

Slide 46 text

Repository - fetch data from network mWeatherNetworkDataSource = weatherNetworkDataSource; LiveData networkData = mWeatherNetworkDataSource.getCurrentWeatherForecasts(); networkData.observeForever(newForecastsFromNetwork -> { mExecutors.diskIO().execute(() -> { mWeatherDao.bulkInsert(newForecastsFromNetwork); Log.d(LOG_TAG, "New values inserted"); }); });

Slide 47

Slide 47 text

Repository - use LiveData private WeatherNetworkDataSource(Context context, AppExecutors executors) { mContext = context; mExecutors = executors; mDownloadedWeatherForecasts = new MutableLiveData(); }

Slide 48

Slide 48 text

Check when to fetch private boolean isFetchNeeded() { Date today = CustomDateUtils.getNormalizedUtcDateForToday(); int count = mWeatherDao.countAllFutureWeather(today); return (count < WeatherNetworkDataSource.NUM_DAYS); }

Slide 49

Slide 49 text

Check when to fetch public synchronized void initializeData() { if (mInitialized) return; mInitialized = true; mExecutors.diskIO().execute(() -> { if (isFetchNeeded()) { startFetchWeatherService(); } }); }

Slide 50

Slide 50 text

And again - observe LiveData public class DetailActivity extends LifecycleActivity { DetailActivityViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... viewModel = ViewModelProviders.of(this).get(DetailActivityViewModel.class); viewModel.getWeather().observe(this, weatherEntry -> { if(weatherEntry!=null) { bindWeatherToUI(weatherEntry); } }); }

Slide 51

Slide 51 text

The whole picture for sample project

Slide 52

Slide 52 text

Paging Library

Slide 53

Slide 53 text

Paging Library flow

Slide 54

Slide 54 text

Data source KeyedDataSource - if you need to use data from item N to fetch item N+1. TiledDataSource - if you need to fetch pages in range of data from any location you choose in your data store

Slide 55

Slide 55 text

DAO for KeyedDataSource @Dao interface UserDao { @Query("SELECT * from user ORDER BY name DESC LIMIT :limit") public abstract List userNameInitial(int limit); @Query("SELECT * from user WHERE name < :key ORDER BY name DESC LIMIT :limit") public abstract List userNameLoadAfter(String key, int limit); @Query("SELECT * from user WHERE name > :key ORDER BY name ASC LIMIT :limit") public abstract List userNameLoadBefore(String key, int limit); }

Slide 56

Slide 56 text

KeyedDataSource implementation public class KeyedUserQueryDataSource extends KeyedDataSource { @Override public boolean isInvalid() { return super.isInvalid(); } @Override public String getKey(@NonNull User item) { return item.getName(); } @Override public List loadInitial(int pageSize) { return mUserDao.userNameInitial(pageSize); } @Override public List loadBefore(@NonNull String userName, int pageSize) { return mUserDao.userNameLoadBefore(userName, pageSize); } @Override public List loadAfter(@Nullable String userName, int pageSize) { return mUserDao.userNameLoadAfter(userName, pageSize); } }

Slide 57

Slide 57 text

TiledDataSource implementation @Dao interface UserDao { @Query("SELECT * FROM user ORDER BY mAge DESC") public abstract TiledDataSource loadUsersByAgeDesc(); }

Slide 58

Slide 58 text

TiledDataSource - under the hood @Dao interface UserDao { @Query("SELECT COUNT(*) from user") public abstract Integer getUserCount(); @Query("SELECT * from user ORDER BY mName DESC LIMIT :limit OFFSET :offset") public abstract List userNameLimitOffset(int limit, int offset); }

Slide 59

Slide 59 text

TiledDataSource - under the hood public class OffsetUserQueryDataSource extends TiledDataSource { @Override public boolean isInvalid() { return super.isInvalid(); } @Override public int countItems() { return mUserDao.getUserCount(); } @Override public List loadRange(int startPosition, int loadCount) { return mUserDao.userNameLimitOffset(loadCount, startPosition); } }

Slide 60

Slide 60 text

Paging Library - PagedList DataSource PagedList - loads its data in chunks (pages) from a DataSource PagedListAdapter LivePagedListProvider

Slide 61

Slide 61 text

Paging Library - LivePagedListProvider DataSource PagedList PagedListAdapter - listens to PagedList loading callbacks as pages are loaded, and uses DiffUtil on a background thread to compute fine grained updates as new PagedLists are received. LivePagedListProvider - provides a LiveData, given a means to construct a DataSource

Slide 62

Slide 62 text

LivePagedListProvider generation by DAO @Dao interface UserDao { @Query("SELECT * FROM user ORDER BY lastName ASC") public abstract LivePagedListProvider usersByLastName(); }

Slide 63

Slide 63 text

Paging Library - PagedListAdapter DataSource PagedList PagedListAdapter - listens to PagedList loading callbacks as pages are loaded, and uses DiffUtil on a background thread to compute fine grained updates as new PagedLists are received. LivePagedListProvider

Slide 64

Slide 64 text

PagedListAdapter usage pattern - DAO @Dao interface UserDao { @Query("SELECT * FROM user ORDER BY lastName ASC") public abstract LivePagedListProvider usersByLastName(); }

Slide 65

Slide 65 text

PagedListAdapter usage pattern - PagedList.Config class MyViewModel extends ViewModel { public final LiveData> usersList; public MyViewModel(UserDao userDao) { usersList = userDao.usersByLastName().create( /* initial load position */ 0, new PagedList.Config.Builder() .setPageSize(50) .setPrefetchDistance(50) .build()); } }

Slide 66

Slide 66 text

PagedListAdapter usage pattern - binding class MyActivity extends AppCompatActivity { @Override public void onCreate(Bundle savedState) { super.onCreate(savedState); MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class); RecyclerView recyclerView = findViewById(R.id.user_list); UserAdapter adapter = new UserAdapter(); viewModel.usersList.observe(this, pagedList -> adapter.setList(pagedList)); recyclerView.setAdapter(adapter); } }

Slide 67

Slide 67 text

PagedListAdapter implementation class UserAdapter extends PagedListAdapter { public UserAdapter() { super(DIFF_CALLBACK); } @Override public void onBindViewHolder(UserViewHolder holder, int position) { User user = getItem(position); if (user != null) { holder.bindTo(user); } else { holder.clear(); } } ...

Slide 68

Slide 68 text

PagedListAdapter implementation class UserAdapter extends PagedListAdapter { ... public static final DiffCallback DIFF_CALLBACK = new DiffCallback() { @Override public boolean areItemsTheSame( @NonNull User oldUser, @NonNull User newUser) { return oldUser.getId() == newUser.getId(); } @Override public boolean areContentsTheSame( @NonNull User oldUser, @NonNull User newUser) { return oldUser.equals(newUser); } } }

Slide 69

Slide 69 text

Paging Library flow

Slide 70

Slide 70 text

Testing Sneak peek :)

Slide 71

Slide 71 text

UI Controller - Instrumentation

Slide 72

Slide 72 text

ViewModel - JUnit

Slide 73

Slide 73 text

Repository - JUnit, MockWebServer

Slide 74

Slide 74 text

Scalability and advices Almost there...

Slide 75

Slide 75 text

What if... I use RxJava? I already have MVP? I love Kotlin? I’m working on legacy project? Typical questions

Slide 76

Slide 76 text

The Answer is...

Slide 77

Slide 77 text

Read, watch and visit... Links

Slide 78

Slide 78 text

Guide to App Architecture https://developer.android.com/topic/libraries/architecture/guide.html Architecture Components https://developer.android.com/topic/libraries/architecture/index.html I/O ‘17 Architecture Components Introduction - https://youtu.be/FrteWKKVyzI Solving the Lifecycle Problem - https://youtu.be/bEKNi1JOrNs Persistence and Offline - https://youtu.be/MfHsPGQ6bgE Architecture Components on GDD Europe - https://youtu.be/Ts-uxYiBEQ8 GDD Europe CodeLabs g.co/codelabs/gdd17 Google Github samples https://github.com/googlesamples/android-architecture-components What to read and watch :)

Slide 79

Slide 79 text

Android MVP Helper https://github.com/Ufkoku/AndroidMVPHelper Moxy https://github.com/Arello-Mobile/Moxy Mosby https://github.com/sockeqwe/mosby Clean Architecture https://github.com/android10/Android-CleanArchitecture Reark https://github.com/reark/reark MVP + Dagger2 + Rx https://android.jlelse.eu/mvp-dagger-2-rx-clean-modern-android-app-code-74f63c9a6f2f Architecture the Lost Years by Uncle Bob https://youtu.be/WpkDN78P884 Alternatives to consider

Slide 80

Slide 80 text

Where to go :)

Slide 81

Slide 81 text

Thank you! :) Constantine Mars @ConstantineMars +ConstantineMars Mobile Architecture Club