AppDevCon 2018
Michelantonio Trizio
@mik3lantoni0
Mike Trizio - @mik3lantoni0 - #nerdipity
Michelantonio Trizio
GDG Bari community lead
Computer Science Engineer
Android Dev
CTO @ Wideverse
Mike Trizio - @mik3lantoni0 - #nerdipity
SPOILER ALERT
Mike Trizio - @mik3lantoni0 - #nerdipity
About architecture components
➔ Announced at Google I/O 2017 and became stable last november
➔ Provide frameworks to create more maintainable and robust app
➔ Foster components decoupling within the app
Mike Trizio - @mik3lantoni0 - #nerdipity
What’s the problem?
Mike Trizio - @mik3lantoni0 - #nerdipity
The solution
Follow architectural principle
➔ Separation of concerns
➔ Drive your UI from a model (preferably persistent one)
Mike Trizio - @mik3lantoni0 - #nerdipity
App architecture
Mike Trizio - @mik3lantoni0 - #nerdipity
SEASON 1
1x01 - Room
1x02 - Lifecycle
1x03 - ViewModel
1x04 - LiveData
1x05 - Paging
Mike Trizio - @mik3lantoni0 - #nerdipity
1x01 - Room
Mike Trizio - @mik3lantoni0 - #nerdipity
Data layer before Room
Mike Trizio - @mik3lantoni0 - #nerdipity
Room architecture diagram
Mike Trizio - @mik3lantoni0 - #nerdipity
Components of room
@Entity: annotation needed define a table
@Dao: annotation needed define a Data Access Object
@Database: annotation to create the database holder
Mike Trizio - @mik3lantoni0 - #nerdipity
Create an entity
@Entity(tableName = "characters")
class Character {
@PrimaryKey (autoGenerate = true)
public int id;
public String firstName;
@NonNull
public String lastName;
@Ignore
Bitmap picture;
}
character table
id (PK) firstName LastName
1 Walter White
2 Jesse Pinkman
3 Gustavo Fring
Mike Trizio - @mik3lantoni0 - #nerdipity
Create a Dao
Annotations available
@Dao
@Insert
@Delete
@Update
@Query
@Dao
public interface CharacterDao {
@Query("SELECT * FROM characters")
List<User> getAll();
@Insert
void insertAll(Character... characters);
@Delete
void delete(Character character);
}
Mike Trizio - @mik3lantoni0 - #nerdipity
Create the Database
@Database(entities = {Character.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase sInstance;
public abstract CharacterDao characterDao(); //Getters for Dao
public static AppDatabase getInstance(Context context) {
if (sInstance == null)
sInstance = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
return sInstance;
}
}
Mike Trizio - @mik3lantoni0 - #nerdipity
get and use Database instance
//GET DATABASE INSTANCE
AppDatabase database = AppDatabase.getInstance(Context context);
//GET ALL CHARACTERS INTO THE DB
List<Character> allCharacters = database.characterDao().getAll();
Mike Trizio - @mik3lantoni0 - #nerdipity
Query example
@Query("SELECT * FROM characters WHERE first_name LIKE :first AND last_name
LIKE :last LIMIT 1")
Character findByName(String first, String last);
Mike Trizio - @mik3lantoni0 - #nerdipity
Transaction
@Dao
public abstract class CharacterDao {
@Transaction
public void updateCharacters(List<Characters> characterList) {
deleteAllCharacter()
insertAll(characterList)
}
@Insert
public insertAll(users: List<User>);
@Query("DELETE FROM Users")
public void deleteAllUsers();
}
Mike Trizio - @mik3lantoni0 - #nerdipity
Migration (1)
@Database(entities = {Character.class}, version = 2) //update the version
public abstract class AppDatabase extends RoomDatabase {
private static SunshineDatabase sInstance;
public abstract CharacterDao characterDao();
public static AppDatabase getInstance(Context context) {
...
}
}
Mike Trizio - @mik3lantoni0 - #nerdipity
Migration (2)
@Entity(tableName = "characters")
class Character {
@PrimaryKey (autoGenerate = true)
public int id;
public String firstName;
public String lastName;
public String alias; //add the alias field
@Ignore
public Bitmap picture;
}
Mike Trizio - @mik3lantoni0 - #nerdipity
Migration (3)
Room.databaseBuilder(getApplicationContext(), MyDb.class,
"database-name").addMigrations(MIGRATION_1_2).build();
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE character add column alias text");
}
}
Mike Trizio - @mik3lantoni0 - #nerdipity
Ask only what you need (1)
public class SimpleSeries {
public int id;
public String title;
}
@Entity(tableName = "series")
public class Series {
@PrimaryKey (autoGenerate = true)
public int id;
public String title;
public String description;
}
Mike Trizio - @mik3lantoni0 - #nerdipity
Ask only what you need (2)
@Query("SELECT series.id, series.title FROM series")
List<SimpleSeries> getSimpleSeries();
Mike Trizio - @mik3lantoni0 - #nerdipity
@Relation and one to many query
public class SeriesAndAllCharacters {
@Embedded
private Series series;
@Relation(parentColumn = “id”,
entityColumn = “seriesId”)
private List<Character> characters;
}
Mike Trizio - @mik3lantoni0 - #nerdipity
@Relation and the Dao
@Transaction
@Query(“SELECT * FROM Series”)
List<SeriesAndAllCharacters> getSeries();
Mike Trizio - @mik3lantoni0 - #nerdipity
Libraries comparison
➔ Realm is the most robust
➔ ObjectBox is fastest
➔ Room is lightest
Mike Trizio - @mik3lantoni0 - #nerdipity
1x02 - Lifecycle
Mike Trizio - @mik3lantoni0 - #nerdipity
Android activity lifecycle
Mike Trizio - @mik3lantoni0 - #nerdipity
going upside down... or simply rotate the phone
Mike Trizio - @mik3lantoni0 - #nerdipity
...a lot of stranger things can happen
Mike Trizio - @mik3lantoni0 - #nerdipity
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());
Lifecycle observer implementation
Mike Trizio - @mik3lantoni0 - #nerdipity
observable events
ON_CREATE ON_DESTROY ON_PAUSE
ON_RESUME ON_START ON_STOP
Mike Trizio - @mik3lantoni0 - #nerdipity
LifecycleOwner
public class MyActivity extends LifecycleActivity {
private MyObeserver myObeserver;
public void onCreate(...) {
myObeserver = new MyObeserver(this, location ->{
// update UI
});
}
since Support Library 26.1.0 AppCompatActivity and Support Fragments implements
LifecycleOwner before that we need to use LifecycleActivity
Mike Trizio - @mik3lantoni0 - #nerdipity
1x03 - ViewModel
Mike Trizio - @mik3lantoni0 - #nerdipity
ViewModels persist configuration change
Mike Trizio - @mik3lantoni0 - #nerdipity
Interaction of entities in an app built with
Architecture components
Mike Trizio - @mik3lantoni0 - #nerdipity
public class CharacterDetailViewModel extends ViewModel {
private Character mCharacter;
public CharacterDetailViewModel(CharacterDao dao, int characterId) {
mCharacter = dao.getDetails(id);
}
public Character getCharacter(){
return mCharacter;
}
}
Create a viewModel (1)
Mike Trizio - @mik3lantoni0 - #nerdipity
Create a viewModel - in the Activity(2)
private CharacterDetailViewModel characterViewModel;
protected void onCreate(Bundle savedInstanceState){
CharacterModelFactory characterFactory =
new CharacterModelFactory(dao, characterId);
characterViewModel =
ViewModelProviders.of(this, characterModelFacotry)
.get(CharacterDetailViewModel.class)
}
}
Mike Trizio - @mik3lantoni0 - #nerdipity
1x04 - LiveData
Mike Trizio - @mik3lantoni0 - #nerdipity
DING DING DING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Mike Trizio - @mik3lantoni0 - #nerdipity
LiveData example
MutableLiveData<String> name =
new MutableLiveData<String>();
name.setValue("Saul");
name.observe(<LIFECYCLE OWNER>,
newName -> {
// Do something when the observer
is triggered; usually updating the
UI
});
Mike Trizio - @mik3lantoni0 - #nerdipity
merge liveData and viewModel
public CharacterDetailViewModel(){
mCharacter = new
MutableLiveData<>();
}
mViewModel.getCharacter().observe(
this, characterEntry -> {
// Update the UI
});
if (characterEntry != null)
bindWeatherToUI(characterEntry);
Mike Trizio - @mik3lantoni0 - #nerdipity
Add a new character live
Mike Trizio - @mik3lantoni0 - #nerdipity
Avoid false positive for observable queries
@Query(“SELECT * FROM Users WHERE characterId = :id”)
LiveData<Character> getCharacterById(long id)
➔ DELETE, UPDATE or INSERT operations on a table generate a trigger
➔ Room creates an InvalidationTracker
➔ LiveData receives the InvalidationTracker and it triggers a re-query
Mike Trizio - @mik3lantoni0 - #nerdipity
Filtering emissions (1)
final MediatorLiveData distinctLiveData = new MediatorLiveData<>();
distinctLiveData.addSource(simpleUserLiveData, new Observer<SimpleUser>() {
...
});
Mike Trizio - @mik3lantoni0 - #nerdipity
Filtering emissions (2)
private SimpleSeries lastSimpleSeries = null;
@Override
public void onChanged(@Nullable SimpleSeries changedSimpleSeries) {
if(lastSimpleSeries == null){
lastSimpleSeries = changedSimpleSeries;
distinctLiveData.postValue(lastSimpleSeries);
}else if((changedSimpleSeries != null && lastSimpleSeries != null)
|| !lastSimpleSeries.equals(changedSimpleSeries)){
lastSimpleSeries = changedSimpleSeries;
distinctLiveData.postValue(lastSimpleSeries);
}
}
}
Mike Trizio - @mik3lantoni0 - #nerdipity
1x05 - Paging
Mike Trizio - @mik3lantoni0 - #nerdipity
when we deal with a lot of data….
Mike Trizio - @mik3lantoni0 - #nerdipity
➔ It’s easier to gradually load
information as needed from a data
source
➔ No overloading the device or
waiting too long for a big database
query
➔ The workflow is mainly in the
background
Only a trailer...
Mike Trizio - @mik3lantoni0 - #nerdipity
Recap
1. Room - provide simple interaction with database
2. Lifecycle - better dealing with Activity Lifecycle
3. ViewModel - way to hold and manage data
4. LiveData - data holder lifecycle aware that allow to observe data
5. Paging - easy and light way to load information as needed
Mike Trizio - @mik3lantoni0 - #nerdipity
what’s next?
Architecture components guide
https://goo.gl/j5Zztk
Architecture talk @ GDD Krakow 2017
https://goo.gl/6C2fvr
Architecture guide codelab
https://goo.gl/gF9Vyu
Mike Trizio - @mik3lantoni0 - #nerdipity
That’s all folks!!!
Mike Trizio - @mik3lantoni0 - #nerdipity
Questions?
Mike Trizio - @mik3lantoni0 - #nerdipity
Thank you!
Rate this talk please
https://goo.gl/TDT5Hb
Follow me
@mik3lantoni0

[App devcon 18] Brace yourself with Android Architecture Components

  • 1.
  • 2.
    Mike Trizio -@mik3lantoni0 - #nerdipity Michelantonio Trizio GDG Bari community lead Computer Science Engineer Android Dev CTO @ Wideverse
  • 3.
    Mike Trizio -@mik3lantoni0 - #nerdipity SPOILER ALERT
  • 4.
    Mike Trizio -@mik3lantoni0 - #nerdipity About architecture components ➔ Announced at Google I/O 2017 and became stable last november ➔ Provide frameworks to create more maintainable and robust app ➔ Foster components decoupling within the app
  • 5.
    Mike Trizio -@mik3lantoni0 - #nerdipity What’s the problem?
  • 6.
    Mike Trizio -@mik3lantoni0 - #nerdipity The solution Follow architectural principle ➔ Separation of concerns ➔ Drive your UI from a model (preferably persistent one)
  • 7.
    Mike Trizio -@mik3lantoni0 - #nerdipity App architecture
  • 8.
    Mike Trizio -@mik3lantoni0 - #nerdipity SEASON 1 1x01 - Room 1x02 - Lifecycle 1x03 - ViewModel 1x04 - LiveData 1x05 - Paging
  • 9.
    Mike Trizio -@mik3lantoni0 - #nerdipity 1x01 - Room
  • 10.
    Mike Trizio -@mik3lantoni0 - #nerdipity Data layer before Room
  • 11.
    Mike Trizio -@mik3lantoni0 - #nerdipity Room architecture diagram
  • 12.
    Mike Trizio -@mik3lantoni0 - #nerdipity Components of room @Entity: annotation needed define a table @Dao: annotation needed define a Data Access Object @Database: annotation to create the database holder
  • 13.
    Mike Trizio -@mik3lantoni0 - #nerdipity Create an entity @Entity(tableName = "characters") class Character { @PrimaryKey (autoGenerate = true) public int id; public String firstName; @NonNull public String lastName; @Ignore Bitmap picture; } character table id (PK) firstName LastName 1 Walter White 2 Jesse Pinkman 3 Gustavo Fring
  • 14.
    Mike Trizio -@mik3lantoni0 - #nerdipity Create a Dao Annotations available @Dao @Insert @Delete @Update @Query @Dao public interface CharacterDao { @Query("SELECT * FROM characters") List<User> getAll(); @Insert void insertAll(Character... characters); @Delete void delete(Character character); }
  • 15.
    Mike Trizio -@mik3lantoni0 - #nerdipity Create the Database @Database(entities = {Character.class}, version = 1) public abstract class AppDatabase extends RoomDatabase { private static AppDatabase sInstance; public abstract CharacterDao characterDao(); //Getters for Dao public static AppDatabase getInstance(Context context) { if (sInstance == null) sInstance = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name").build(); return sInstance; } }
  • 16.
    Mike Trizio -@mik3lantoni0 - #nerdipity get and use Database instance //GET DATABASE INSTANCE AppDatabase database = AppDatabase.getInstance(Context context); //GET ALL CHARACTERS INTO THE DB List<Character> allCharacters = database.characterDao().getAll();
  • 17.
    Mike Trizio -@mik3lantoni0 - #nerdipity Query example @Query("SELECT * FROM characters WHERE first_name LIKE :first AND last_name LIKE :last LIMIT 1") Character findByName(String first, String last);
  • 18.
    Mike Trizio -@mik3lantoni0 - #nerdipity Transaction @Dao public abstract class CharacterDao { @Transaction public void updateCharacters(List<Characters> characterList) { deleteAllCharacter() insertAll(characterList) } @Insert public insertAll(users: List<User>); @Query("DELETE FROM Users") public void deleteAllUsers(); }
  • 20.
    Mike Trizio -@mik3lantoni0 - #nerdipity Migration (1) @Database(entities = {Character.class}, version = 2) //update the version public abstract class AppDatabase extends RoomDatabase { private static SunshineDatabase sInstance; public abstract CharacterDao characterDao(); public static AppDatabase getInstance(Context context) { ... } }
  • 21.
    Mike Trizio -@mik3lantoni0 - #nerdipity Migration (2) @Entity(tableName = "characters") class Character { @PrimaryKey (autoGenerate = true) public int id; public String firstName; public String lastName; public String alias; //add the alias field @Ignore public Bitmap picture; }
  • 22.
    Mike Trizio -@mik3lantoni0 - #nerdipity Migration (3) Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name").addMigrations(MIGRATION_1_2).build(); static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL("ALTER TABLE character add column alias text"); } }
  • 23.
    Mike Trizio -@mik3lantoni0 - #nerdipity Ask only what you need (1) public class SimpleSeries { public int id; public String title; } @Entity(tableName = "series") public class Series { @PrimaryKey (autoGenerate = true) public int id; public String title; public String description; }
  • 24.
    Mike Trizio -@mik3lantoni0 - #nerdipity Ask only what you need (2) @Query("SELECT series.id, series.title FROM series") List<SimpleSeries> getSimpleSeries();
  • 25.
    Mike Trizio -@mik3lantoni0 - #nerdipity @Relation and one to many query public class SeriesAndAllCharacters { @Embedded private Series series; @Relation(parentColumn = “id”, entityColumn = “seriesId”) private List<Character> characters; }
  • 26.
    Mike Trizio -@mik3lantoni0 - #nerdipity @Relation and the Dao @Transaction @Query(“SELECT * FROM Series”) List<SeriesAndAllCharacters> getSeries();
  • 27.
    Mike Trizio -@mik3lantoni0 - #nerdipity Libraries comparison ➔ Realm is the most robust ➔ ObjectBox is fastest ➔ Room is lightest
  • 28.
    Mike Trizio -@mik3lantoni0 - #nerdipity 1x02 - Lifecycle
  • 29.
    Mike Trizio -@mik3lantoni0 - #nerdipity Android activity lifecycle
  • 30.
    Mike Trizio -@mik3lantoni0 - #nerdipity going upside down... or simply rotate the phone
  • 31.
    Mike Trizio -@mik3lantoni0 - #nerdipity ...a lot of stranger things can happen
  • 32.
    Mike Trizio -@mik3lantoni0 - #nerdipity 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()); Lifecycle observer implementation
  • 33.
    Mike Trizio -@mik3lantoni0 - #nerdipity observable events ON_CREATE ON_DESTROY ON_PAUSE ON_RESUME ON_START ON_STOP
  • 34.
    Mike Trizio -@mik3lantoni0 - #nerdipity LifecycleOwner public class MyActivity extends LifecycleActivity { private MyObeserver myObeserver; public void onCreate(...) { myObeserver = new MyObeserver(this, location ->{ // update UI }); } since Support Library 26.1.0 AppCompatActivity and Support Fragments implements LifecycleOwner before that we need to use LifecycleActivity
  • 35.
    Mike Trizio -@mik3lantoni0 - #nerdipity 1x03 - ViewModel
  • 36.
    Mike Trizio -@mik3lantoni0 - #nerdipity ViewModels persist configuration change
  • 37.
    Mike Trizio -@mik3lantoni0 - #nerdipity Interaction of entities in an app built with Architecture components
  • 38.
    Mike Trizio -@mik3lantoni0 - #nerdipity public class CharacterDetailViewModel extends ViewModel { private Character mCharacter; public CharacterDetailViewModel(CharacterDao dao, int characterId) { mCharacter = dao.getDetails(id); } public Character getCharacter(){ return mCharacter; } } Create a viewModel (1)
  • 39.
    Mike Trizio -@mik3lantoni0 - #nerdipity Create a viewModel - in the Activity(2) private CharacterDetailViewModel characterViewModel; protected void onCreate(Bundle savedInstanceState){ CharacterModelFactory characterFactory = new CharacterModelFactory(dao, characterId); characterViewModel = ViewModelProviders.of(this, characterModelFacotry) .get(CharacterDetailViewModel.class) } }
  • 40.
    Mike Trizio -@mik3lantoni0 - #nerdipity 1x04 - LiveData
  • 41.
    Mike Trizio -@mik3lantoni0 - #nerdipity DING DING DING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  • 42.
    Mike Trizio -@mik3lantoni0 - #nerdipity LiveData example MutableLiveData<String> name = new MutableLiveData<String>(); name.setValue("Saul"); name.observe(<LIFECYCLE OWNER>, newName -> { // Do something when the observer is triggered; usually updating the UI });
  • 43.
    Mike Trizio -@mik3lantoni0 - #nerdipity merge liveData and viewModel public CharacterDetailViewModel(){ mCharacter = new MutableLiveData<>(); } mViewModel.getCharacter().observe( this, characterEntry -> { // Update the UI }); if (characterEntry != null) bindWeatherToUI(characterEntry);
  • 44.
    Mike Trizio -@mik3lantoni0 - #nerdipity Add a new character live
  • 45.
    Mike Trizio -@mik3lantoni0 - #nerdipity Avoid false positive for observable queries @Query(“SELECT * FROM Users WHERE characterId = :id”) LiveData<Character> getCharacterById(long id) ➔ DELETE, UPDATE or INSERT operations on a table generate a trigger ➔ Room creates an InvalidationTracker ➔ LiveData receives the InvalidationTracker and it triggers a re-query
  • 46.
    Mike Trizio -@mik3lantoni0 - #nerdipity Filtering emissions (1) final MediatorLiveData distinctLiveData = new MediatorLiveData<>(); distinctLiveData.addSource(simpleUserLiveData, new Observer<SimpleUser>() { ... });
  • 47.
    Mike Trizio -@mik3lantoni0 - #nerdipity Filtering emissions (2) private SimpleSeries lastSimpleSeries = null; @Override public void onChanged(@Nullable SimpleSeries changedSimpleSeries) { if(lastSimpleSeries == null){ lastSimpleSeries = changedSimpleSeries; distinctLiveData.postValue(lastSimpleSeries); }else if((changedSimpleSeries != null && lastSimpleSeries != null) || !lastSimpleSeries.equals(changedSimpleSeries)){ lastSimpleSeries = changedSimpleSeries; distinctLiveData.postValue(lastSimpleSeries); } } }
  • 48.
    Mike Trizio -@mik3lantoni0 - #nerdipity 1x05 - Paging
  • 49.
    Mike Trizio -@mik3lantoni0 - #nerdipity when we deal with a lot of data….
  • 50.
    Mike Trizio -@mik3lantoni0 - #nerdipity ➔ It’s easier to gradually load information as needed from a data source ➔ No overloading the device or waiting too long for a big database query ➔ The workflow is mainly in the background Only a trailer...
  • 51.
    Mike Trizio -@mik3lantoni0 - #nerdipity Recap 1. Room - provide simple interaction with database 2. Lifecycle - better dealing with Activity Lifecycle 3. ViewModel - way to hold and manage data 4. LiveData - data holder lifecycle aware that allow to observe data 5. Paging - easy and light way to load information as needed
  • 52.
    Mike Trizio -@mik3lantoni0 - #nerdipity what’s next? Architecture components guide https://goo.gl/j5Zztk Architecture talk @ GDD Krakow 2017 https://goo.gl/6C2fvr Architecture guide codelab https://goo.gl/gF9Vyu
  • 53.
    Mike Trizio -@mik3lantoni0 - #nerdipity That’s all folks!!!
  • 54.
    Mike Trizio -@mik3lantoni0 - #nerdipity Questions?
  • 55.
    Mike Trizio -@mik3lantoni0 - #nerdipity Thank you! Rate this talk please https://goo.gl/TDT5Hb Follow me @mik3lantoni0