Share
Embed
Architecture Components - Build your app right way and enjoy IT! :) - GDG Kharkiv-Center
Talk about Architecture Components at GDG Kharkiv-Center Meetup (GDD Extended)
Royce Mars
October 07, 2017
Transcript
67 slides
-
Architecture Components Build your app right way and enjoy IT!
:) Constantine Mars Team Lead, Senior Developer @ DataArt -
#gdg_kharkiv_center Android Has “Good Bones” Components: -Activities -Fragments -Services -Content
Providers -Broadcast Receivers -
#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 -
#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 -
#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 -
#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 -
#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 -
#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 -
#gdg_kharkiv_center ViewModel Data holder for Activity/Fragment Survives configuration changes NEVER
references View / Activity / Fragment -
#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 -
#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 -
#gdg_kharkiv_center LiveData Represent data needed for the UI to display
An observable data holder Lifecycle aware Automatic subscription management -
#gdg_kharkiv_center public class DetailActivityViewModel extends ViewModel { private MutableLiveData<WeatherEntry> mWeather;
public DetailActivityViewModel() {} public MutableLiveData<WeatherEntry> getWeather() { return mWeather; } public void setWeather(WeatherEntry weatherEntry) { mWeather.postValue(weatherEntry); } } Live Data LiveData -
#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 -
#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 -
#gdg_kharkiv_center Remote Network Data Source Manages data from a remote
data source, such as the internet May use REST, Cloud -
#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 -
#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 -
#gdg_kharkiv_center @Entity(tableName = "weather", indices = {@Index(value = {"date"}, unique
= true)}) public class WeatherEntry { @PrimaryKey(autoGenerate = true) private int id; … } @Entity declaration -
#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 -
#gdg_kharkiv_center @Dao public interface WeatherDao { @Query("SELECT * FROM weather")
List<WeatherEntry> getAll(); @Insert(onConflict = OnConflictStrategy.REPLACE) void insertAll(WeatherEntry... weatherEntries); @Delete void delete(WeatherEntry weatherEntry); } @Dao declaration -
#gdg_kharkiv_center @Database(entities = {WeatherEntry.class}, version = 1) @TypeConverters(DateConverter.class) public abstract
class AppDatabase extends RoomDatabase { public abstract WeatherDao weatherDao(); } @Database declaration -
#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 -
#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 -
#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 -
#gdg_kharkiv_center mWeatherNetworkDataSource = weatherNetworkDataSource; LiveData<WeatherEntry[]> 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 -
#gdg_kharkiv_center private WeatherNetworkDataSource(Context context, AppExecutors executors) { mContext = context;
mExecutors = executors; mDownloadedWeatherForecasts = new MutableLiveData<WeatherEntry[]>(); } Repository - use LiveData LiveData -
#gdg_kharkiv_center private boolean isFetchNeeded() { Date today = CustomDateUtils.getNormalizedUtcDateForToday(); int
count = mWeatherDao.countAllFutureWeather(today); return (count < WeatherNetworkDataSource.NUM_DAYS); } Check when to fetch -
#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 -
#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 -
#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 -
#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 -
#gdg_kharkiv_center Paging Library - Annotations on DAO @Query("select * from
users WHERE age > :age order by name DESC, id ASC") TiledDataSource<User> usersOlderThan(int age); @Query("SELECT * from users order WHERE age > :age order by name DESC, id ASC") public abstract LivePagedListProvider<Integer, User> usersOlderThan(int age); -
#gdg_kharkiv_center Typical questions What if... I use RxJava? I already
have MVP? I love Kotlin? I’m working on legacy project? -
#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 -
#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