Slide 1

Slide 1 text

Architecture Components Build your app right way and enjoy IT! :) Constantine Mars Team Lead, Senior Developer @ DataArt

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

#gdg_kharkiv_center But… There are Everyday Problems to Solve 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

Slide 4

Slide 4 text

#gdg_kharkiv_center Simply related to Activity Lifecycle

Slide 5

Slide 5 text

#gdg_kharkiv_center Common Principles for staying in the mind :) -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

Slide 6

Slide 6 text

#gdg_kharkiv_center Focus on Separation of Concerns... image from fernandocejas.com

Slide 7

Slide 7 text

#gdg_kharkiv_center Consider Clean Architecture approach... image from fernandocejas.com

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

#gdg_kharkiv_center And Finally - Android Recommended Architecture

Slide 10

Slide 10 text

#gdg_kharkiv_center Android Recommended Architecture. Another View image from Guide to App Architecture

Slide 11

Slide 11 text

#gdg_kharkiv_center Be together. not the same

Slide 12

Slide 12 text

#gdg_kharkiv_center Be together. not the same “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.” Quote from Guide to App Architecture

Slide 13

Slide 13 text

Architecture Components Building blocks

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

#gdg_kharkiv_center LifecycleActivity, LifecycleFragment After the lifecycles API in the Architecture Components becomes stable, the Fragment class in the Android Support Library will implement LifecycleOwner

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

#gdg_kharkiv_center ViewModel and Lifecycle

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

#gdg_kharkiv_center LiveData event propagation

Slide 22

Slide 22 text

#gdg_kharkiv_center LiveData sample implementation

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

#gdg_kharkiv_center 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); } }); } Live Data observing Observe

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

#gdg_kharkiv_center Model Manages local data stored in the database

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Room ORooM :)

Slide 30

Slide 30 text

#gdg_kharkiv_center Room?

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

#gdg_kharkiv_center Room annotations -@Entity -@Dao -@Database

Slide 33

Slide 33 text

#gdg_kharkiv_center @Entity

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

#gdg_kharkiv_center //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 ... @Entity constructors

Slide 36

Slide 36 text

#gdg_kharkiv_center @Dao

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

#gdg_kharkiv_center @Database

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

#gdg_kharkiv_center 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; } @Database - a singleton

Slide 41

Slide 41 text

#gdg_kharkiv_center 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(); } } Type converters

Slide 42

Slide 42 text

#gdg_kharkiv_center Alternatives

Slide 43

Slide 43 text

Repository Single source of trust

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

#gdg_kharkiv_center Display the data

Slide 50

Slide 50 text

#gdg_kharkiv_center 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); } }); } And again - observe LiveData

Slide 51

Slide 51 text

Testing Sneak peek :)

Slide 52

Slide 52 text

#gdg_kharkiv_center UI Controller - Instrumentation

Slide 53

Slide 53 text

#gdg_kharkiv_center ViewModel - JUnit

Slide 54

Slide 54 text

#gdg_kharkiv_center Repository - JUnit, MockWebServer

Slide 55

Slide 55 text

Latest updates From Alpha to Beta

Slide 56

Slide 56 text

#gdg_kharkiv_center September - October updates October 5, 2017 - Beta 2 - improved LiveDataReactiveStreams, - FullLifecycleObserver for Java 8, - Handling @Query methods with @NonNull annotations, - Still requires proguard settings to keep android.arch.lifecycle.GeneratedAdapter 1.0.0 Alpha 9-1 - Support Library 26.1.0, AppCompatActivity and Support Fragment now implement the LifecycleOwner interface - LifecycleActivity & LifecycleFragment are now deprecated - New Library: Paging

Slide 57

Slide 57 text

#gdg_kharkiv_center Paging Library - DataSource, - PagedList - PagedListAdapter - KeyedDataSource if you need to use data from item N-1 to load item N - TiledDataSource - LivePagedListProvider

Slide 58

Slide 58 text

#gdg_kharkiv_center Paging Library - Annotations on DAO @Query("select * from users WHERE age > :age order by name DESC, id ASC") TiledDataSource usersOlderThan(int age); @Query("SELECT * from users order WHERE age > :age order by name DESC, id ASC") public abstract LivePagedListProvider usersOlderThan(int age);

Slide 59

Slide 59 text

#gdg_kharkiv_center Paging Library - flow

Slide 60

Slide 60 text

Almost there... Scalability

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

#gdg_kharkiv_center The Answer is...

Slide 63

Slide 63 text

#gdg_kharkiv_center What to read :) 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

Slide 64

Slide 64 text

#gdg_kharkiv_center Alternatives to consider Moxy https://github.com/Arello-Mobile/Moxy Mosby https://github.com/sockeqwe/mosby Android MVP Helper https://github.com/Ufkoku/AndroidMVPHelper 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

Slide 65

Slide 65 text

#gdg_kharkiv_center Where to go :)

Slide 66

Slide 66 text

#gdg_kharkiv_center Where to go :)

Slide 67

Slide 67 text

Constantine Mars @ConstantineMars +ConstantineMars Thank you! Q&A Mobile Architecture Club