4. 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
6. -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 :)
9. -Modular app
-Each class responsible for one well-defined function
-Should be no god objects
-The app should be testable
Remember Good Architecture Goals...
13. “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
15. Purpose: Display data and pass on UI events
Neither contain the UI data, nor directly manipulate data
Examples: Activity, Fragment
Views = UI Controllers = LifecycleOwners
25. Live Data
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);
}
}
26. 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); }
});
}
27. -Single source of truth
-ViewModels simply request data
from the repository
-Is a mediator between
the different data sources
Repository
Image from fernandocejas.com
28. Manages data from a remote data
source, such as the internet
May use REST, Cloud
Remote Network Data Source
30. 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
35. @Entity declaration
@Entity(tableName = "weather",
indices = {@Index(value = {"date"}, unique = true)})
public class WeatherEntry {
@PrimaryKey(autoGenerate = true)
private int id;
…
}
36. @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
...
40. @Database declaration
@Database(entities = {WeatherEntry.class}, version = 1)
@TypeConverters(DateConverter.class)
public abstract class AppDatabase extends RoomDatabase {
public abstract WeatherDao weatherDao();
}
41. @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;
}
42. 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();
}
}
54. 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
55. DAO for KeyedDataSource
@Dao
interface UserDao {
@Query("SELECT * from user ORDER BY name DESC LIMIT :limit")
public abstract List<User> userNameInitial(int limit);
@Query("SELECT * from user WHERE name < :key ORDER BY name DESC LIMIT :limit")
public abstract List<User> userNameLoadAfter(String key, int limit);
@Query("SELECT * from user WHERE name > :key ORDER BY name ASC LIMIT :limit")
public abstract List<User> userNameLoadBefore(String key, int limit);
}
56. KeyedDataSource implementation
public class KeyedUserQueryDataSource extends KeyedDataSource<String, User> {
@Override public boolean isInvalid() { return super.isInvalid(); }
@Override public String getKey(@NonNull User item) { return item.getName(); }
@Override public List<User> loadInitial(int pageSize) {
return mUserDao.userNameInitial(pageSize); }
@Override public List<User> loadBefore(@NonNull String userName, int pageSize) {
return mUserDao.userNameLoadBefore(userName, pageSize); }
@Override public List<User> loadAfter(@Nullable String userName, int pageSize) {
return mUserDao.userNameLoadAfter(userName, pageSize); }
}
58. 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<User> userNameLimitOffset(int limit, int offset);
}
59. TiledDataSource - under the hood
public class OffsetUserQueryDataSource extends TiledDataSource<User> {
@Override public boolean isInvalid() { return super.isInvalid(); }
@Override public int countItems() { return mUserDao.getUserCount(); }
@Override public List<User> loadRange(int startPosition, int loadCount) {
return mUserDao.userNameLimitOffset(loadCount, startPosition);
}
}
60. Paging Library - PagedList
DataSource
PagedList - loads its data in chunks (pages) from a DataSource
PagedListAdapter
LivePagedListProvider
61. 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<PagedList>, given a
means to construct a DataSource
62. LivePagedListProvider generation by DAO
@Dao
interface UserDao {
@Query("SELECT * FROM user ORDER BY lastName ASC")
public abstract LivePagedListProvider<Integer, User> usersByLastName();
}
63. 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
64. PagedListAdapter usage pattern - DAO
@Dao
interface UserDao {
@Query("SELECT * FROM user ORDER BY lastName ASC")
public abstract LivePagedListProvider<Integer, User> usersByLastName();
}
65. PagedListAdapter usage pattern -
PagedList.Config
class MyViewModel extends ViewModel {
public final LiveData<PagedList<User>> usersList;
public MyViewModel(UserDao userDao) {
usersList = userDao.usersByLastName().create(
/* initial load position */ 0,
new PagedList.Config.Builder()
.setPageSize(50)
.setPrefetchDistance(50)
.build());
}
}
78. 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 :)
79. 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