The best practices approach for organizing Android applications into logical components has been widely debated by the developer community over the last several years. If you’ve had trouble choosing between MVC, MVP, MVVM and Reactive Architectures, or even understanding how they differ exactly, you’re not alone! Up until now, there has been no official guidance from Google, however at IO’17, Google announced Android Architecture Components as a recommended pattern moving forward. In this session you’ll learn how these architectural patterns relate to each other and the motivations behind each. You’ll also learn how to apply Android Architecture Components effectively through from live code and interactive demonstrations.
4. Architecture
I can be tested
New Developers know exactly
where all my parts are and
how to update me!!
I’m composable!
5. Architectures
Understand the principles and components
• MVC on Android
• MVP
• MVVM + Data Binding
• Reactive Architecture
• Android Architecture Components
• Lifecycle
• ViewModel
• LiveData
• Room
https://github.com/ericmaxwell2003/android-architecture-samples
6. MVC on Android
• Model
Data + State + Business logic
• View
User Interface, visual representation of the model
• Controller
Glue to coordinate interactions between the model and the view
7. MVC - Model
public class Board {
private Cell[][] cells = new Cell[3][3];
private Player winner;
private GameState state;
private Player currentTurn;
private enum GameState { IN_PROGRESS, FINISHED };
public Board() {...}
/**
* Restart or start a new game, will clear the board and win status
*/
public void restart() {...}
/**
* Mark the current row for the player who's current turn it is.
* Will perform no-op if the arguments are out of range or if that position is already played.
* Will also perform a no-op if the game is already over.
*
* @param row 0..2
* @param col 0..2
* @return the player that moved or null if we did not move anything.
*
*/
public Player mark( int row, int col ) {...}
public Player getWinner() {...}
private void clearCells() {...}
private void flipCurrentTurn() {...}
private boolean isValid(int row, int col ) {...}
private boolean isOutOfBounds(int idx){...}
private boolean isCellValueAlreadySet(int row, int col) {...}
private boolean isWinningMoveByPlayer(Player player, int currentRow, int currentCol) {...}
}
public class Cell {
private Player value;
public Player getValue() { return value; }
public void setValue(Player value) { this.value = value; }
}
public enum Player { X , O }
9. MVC - Controller
public class TicTacToeController extends AppCompatActivity {
private Board model;
private ViewGroup buttonGrid;
private View winnerPlayerViewGroup;
private TextView winnerPlayerLabel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tictactoe);
winnerPlayerLabel = (TextView) findViewById(R.id.winnerPlayerLabel);
...
model = new Board();
}
public void onCellClicked(View v) {
Button button = (Button) v;
String tag = button.getTag().toString();
int row = Integer.valueOf(tag.substring(0,1));
int col = Integer.valueOf(tag.substring(1,2));
Player playerThatMoved = model.mark(row, col);
if(playerThatMoved != null) {
button.setText(playerThatMoved.toString());
if (model.getWinner() != null) {
winnerPlayerLabel.setText(playerThatMoved.toString());
winnerPlayerViewGroup.setVisibility(View.VISIBLE);
}
}
}
private void reset() {
winnerPlayerViewGroup.setVisibility(View.GONE);
winnerPlayerLabel.setText("");
model.restart();
for( int i = 0; i < buttonGrid.getChildCount(); i++ ) {
((Button) buttonGrid.getChildAt(i)).setText("");
}
}
}
10. MVC - Testing Model
public class TicTacToeTests {
private Board board;
@Before
public void setup() {
board = new Board();
}
/**
* This test will simulate and verify x is the winner.
*
* X | X | X
* O | |
* | O |
*/
@Test
public void test3inRowAcrossTopForX() {
board.mark(0,0); // x
assertNull(board.getWinner());
board.mark(1,0); // o
assertNull(board.getWinner());
board.mark(0,1); // x
assertNull(board.getWinner());
board.mark(2,1); // o
assertNull(board.getWinner());
board.mark(0,2); // x
assertEquals(Player.X, board.getWinner());
}
/**
* This test will simulate and verify o is the winner.
*
* O | X | X
* | O |
* | X | O
*/
@Test
public void test3inRowDiagonalFromTopLeftToBottomForO() {...}
}
11. MVC - Testing Controller
public class TicTacToeController extends AppCompatActivity {
private Board model;
private ViewGroup buttonGrid;
private View winnerPlayerViewGroup;
private TextView winnerPlayerLabel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tictactoe);
winnerPlayerLabel = (TextView) findViewById(R.id.winnerPlayerLabel);
...
model = new Board();
}
public void onCellClicked(View v) {
Button button = (Button) v;
String tag = button.getTag().toString();
int row = Integer.valueOf(tag.substring(0,1));
int col = Integer.valueOf(tag.substring(1,2));
Player playerThatMoved = model.mark(row, col);
if(playerThatMoved != null) {
button.setText(playerThatMoved.toString());
if (model.getWinner() != null) {
winnerPlayerLabel.setText(playerThatMoved.toString());
winnerPlayerViewGroup.setVisibility(View.VISIBLE);
}
}
}
private void reset() {
winnerPlayerViewGroup.setVisibility(View.GONE);
winnerPlayerLabel.setText("");
model.restart();
for( int i = 0; i < buttonGrid.getChildCount(); i++ ) {
((Button) buttonGrid.getChildAt(i)).setText("");
}
}
}
12. MVC - Testing Controller
public class TicTacToeController extends AppCompatActivity {
private Board model;
private ViewGroup buttonGrid;
private View winnerPlayerViewGroup;
private TextView winnerPlayerLabel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tictactoe);
winnerPlayerLabel = (TextView) findViewById(R.id.winnerPlayerLabel);
...
model = new Board();
}
public void onCellClicked(View v) {
Button button = (Button) v;
String tag = button.getTag().toString();
int row = Integer.valueOf(tag.substring(0,1));
int col = Integer.valueOf(tag.substring(1,2));
Player playerThatMoved = model.mark(row, col);
if(playerThatMoved != null) {
button.setText(playerThatMoved.toString());
if (model.getWinner() != null) {
winnerPlayerLabel.setText(playerThatMoved.toString());
winnerPlayerViewGroup.setVisibility(View.VISIBLE);
}
}
}
private void reset() {
winnerPlayerViewGroup.setVisibility(View.GONE);
winnerPlayerLabel.setText("");
model.restart();
for( int i = 0; i < buttonGrid.getChildCount(); i++ ) {
((Button) buttonGrid.getChildAt(i)).setText("");
}
}
}
Views, Lifecycle, Interactions
all need mocked
13. MVP
• Model
Data + State + Business logic
• View
User Interface, visual representation of the model
• Presenter
Glue to coordinate interactions between the model and the view
Tells the View what to do, not how to do it!
14. MVP - Model
public class Board {
private Cell[][] cells = new Cell[3][3];
private Player winner;
private GameState state;
private Player currentTurn;
private enum GameState { IN_PROGRESS, FINISHED };
public Board() {...}
/**
* Restart or start a new game, will clear the board and win status
*/
public void restart() {...}
/**
* Mark the current row for the player who's current turn it is.
* Will perform no-op if the arguments are out of range or if that position is already played.
* Will also perform a no-op if the game is already over.
*
* @param row 0..2
* @param col 0..2
* @return the player that moved or null if we did not move anything.
*
*/
public Player mark( int row, int col ) {...}
public Player getWinner() {...}
private void clearCells() {...}
private void flipCurrentTurn() {...}
private boolean isValid(int row, int col ) {...}
private boolean isOutOfBounds(int idx){...}
private boolean isCellValueAlreadySet(int row, int col) {...}
private boolean isWinningMoveByPlayer(Player player, int currentRow, int currentCol) {...}
}
public class Cell {
private Player value;
public Player getValue() { return value; }
public void setValue(Player value) { this.value = value; }
}
public enum Player { X , O }
16. MVP - Controller
public class TicTacToeController extends AppCompatActivity {
private Board model;
private ViewGroup buttonGrid;
private View winnerPlayerViewGroup;
private TextView winnerPlayerLabel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tictactoe);
winnerPlayerLabel = (TextView) findViewById(R.id.winnerPlayerLabel);
...
model = new Board();
}
public void onCellClicked(View v) {
Button button = (Button) v;
String tag = button.getTag().toString();
int row = Integer.valueOf(tag.substring(0,1));
int col = Integer.valueOf(tag.substring(1,2));
Player playerThatMoved = model.mark(row, col);
if(playerThatMoved != null) {
button.setText(playerThatMoved.toString());
if (model.getWinner() != null) {
winnerPlayerLabel.setText(playerThatMoved.toString());
winnerPlayerViewGroup.setVisibility(View.VISIBLE);
}
}
}
private void reset() {
winnerPlayerViewGroup.setVisibility(View.GONE);
winnerPlayerLabel.setText("");
model.restart();
for( int i = 0; i < buttonGrid.getChildCount(); i++ ) {
((Button) buttonGrid.getChildAt(i)).setText("");
}
}
}
17. MVP - Presenter
public class TicTacToeActivity extends AppCompatActivity implements TicTacToeView {
private static String TAG = TicTacToeActivity.class.getName();
private ViewGroup buttonGrid;
private View winnerPlayerViewGroup;
private TextView winnerPlayerLabel;
TicTacToePresenter presenter = new TicTacToePresenter(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tictactoe);
...
presenter.onCreate();
}
@Override
protected void onPause() {
super.onPause();
presenter.onPause();
}
@Override
protected void onResume() {
super.onResume();
presenter.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.onDestroy();
}
public void onCellClicked(View v) {
Button button = (Button) v;
String tag = button.getTag().toString();
int row = Integer.valueOf(tag.substring(0,1));
int col = Integer.valueOf(tag.substring(1,2));
Log.i(TAG, "Click Row: [" + row + "," + col + "]");
presenter.onButtonSelected(row, col);
}
// View Interface Implementation
public void setButtonText(int row, int col, String text) {...}
public void clearButtons() {...}
public void showWinner(String winningPlayerDisplayLabel) {...}
public void clearWinnerDisplay() {...}
}
View
public class TicTacToePresenter extends Presenter {
private TicTacToeView view;
private Board model;
public TicTacToePresenter(TicTacToeView view) {
this.view = view;
this.model = new Board();
}
@Override
public void onCreate() {
model = new Board();
}
public void onButtonSelected(int row, int col) {
Player playerThatMoved = model.mark(row, col);
if(playerThatMoved != null) {
view.setButtonText(row, col, playerThatMoved.toString());
if (model.getWinner() != null) {
view.showWinner(playerThatMoved.toString());
}
}
}
public void onResetSelected() {
view.clearWinnerDisplay();
view.clearButtons();
model.restart();
}
}
public abstract class Presenter {
public void onCreate() {}
public void onPause() {}
public void onResume() {}
public void onDestroy() {}
}
Presenter
18. MVP - Presenter
public class TicTacToeActivity extends AppCompatActivity implements TicTacToeView {
private static String TAG = TicTacToeActivity.class.getName();
private ViewGroup buttonGrid;
private View winnerPlayerViewGroup;
private TextView winnerPlayerLabel;
TicTacToePresenter presenter = new TicTacToePresenter(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tictactoe);
...
presenter.onCreate();
}
@Override
protected void onPause() {
super.onPause();
presenter.onPause();
}
@Override
protected void onResume() {
super.onResume();
presenter.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.onDestroy();
}
public void onCellClicked(View v) {
Button button = (Button) v;
String tag = button.getTag().toString();
int row = Integer.valueOf(tag.substring(0,1));
int col = Integer.valueOf(tag.substring(1,2));
Log.i(TAG, "Click Row: [" + row + "," + col + "]");
presenter.onButtonSelected(row, col);
}
// View Interface Implementation
public void setButtonText(int row, int col, String text) {...}
public void clearButtons() {...}
public void showWinner(String winningPlayerDisplayLabel) {...}
public void clearWinnerDisplay() {...}
}
View
public class TicTacToePresenter extends Presenter {
private TicTacToeView view;
private Board model;
public TicTacToePresenter(TicTacToeView view) {
this.view = view;
this.model = new Board();
}
@Override
public void onCreate() {
model = new Board();
}
public void onButtonSelected(int row, int col) {
Player playerThatMoved = model.mark(row, col);
if(playerThatMoved != null) {
view.setButtonText(row, col, playerThatMoved.toString());
if (model.getWinner() != null) {
view.showWinner(playerThatMoved.toString());
}
}
}
public void onResetSelected() {
view.clearWinnerDisplay();
view.clearButtons();
model.restart();
}
}
public interface TicTacToeView {
void showWinner(String winningPlayerDisplayLabel);
void clearWinnerDisplay();
void clearButtons();
void setButtonText(int row, int col, String text);
}
Presenter
19. MVP - Presenter
public class TicTacToeActivity extends AppCompatActivity implements TicTacToeView {
private static String TAG = TicTacToeActivity.class.getName();
private ViewGroup buttonGrid;
private View winnerPlayerViewGroup;
private TextView winnerPlayerLabel;
TicTacToePresenter presenter = new TicTacToePresenter(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tictactoe);
...
presenter.onCreate();
}
@Override
protected void onPause() {
super.onPause();
presenter.onPause();
}
@Override
protected void onResume() {
super.onResume();
presenter.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.onDestroy();
}
public void onCellClicked(View v) {
Button button = (Button) v;
String tag = button.getTag().toString();
int row = Integer.valueOf(tag.substring(0,1));
int col = Integer.valueOf(tag.substring(1,2));
Log.i(TAG, "Click Row: [" + row + "," + col + "]");
presenter.onButtonSelected(row, col);
}
// View Interface Implementation
public void setButtonText(int row, int col, String text) {...}
public void clearButtons() {...}
public void showWinner(String winningPlayerDisplayLabel) {...}
public void clearWinnerDisplay() {...}
}
View
public class TicTacToePresenter extends Presenter {
private TicTacToeView view;
private Board model;
public TicTacToePresenter(TicTacToeView view) {
this.view = view;
this.model = new Board();
}
@Override
public void onCreate() {
model = new Board();
}
public void onButtonSelected(int row, int col) {
Player playerThatMoved = model.mark(row, col);
if(playerThatMoved != null) {
view.setButtonText(row, col, playerThatMoved.toString());
if (model.getWinner() != null) {
view.showWinner(playerThatMoved.toString());
}
}
}
public void onResetSelected() {
view.clearWinnerDisplay();
view.clearButtons();
model.restart();
}
}
public abstract class Presenter {
public void onCreate() {}
public void onPause() {}
public void onResume() {}
public void onDestroy() {}
}
Presenter
20. MVP - Testing Presenter
@RunWith(MockitoJUnitRunner.class)
public class TicTacToePresenterTests {
private TicTacToePresenter presenter;
@Mock
private TicTacToeView view;
@Before
public void setup() { presenter = new TicTacToePresenter(view); }
/**
* This test will simulate and verify o is the winner.
*
* X | X | X
* O | |
* | O |
*/
@Test
public void test3inRowAcrossTopForX() {
clickAndAssertValueAt(0,0, "X");
verify(view, never()).showWinner(anyString());
clickAndAssertValueAt(1,0, "O");
verify(view, never()).showWinner(anyString());
clickAndAssertValueAt(0,1, "X");
verify(view, never()).showWinner(anyString());
clickAndAssertValueAt(2,1, "O");
verify(view, never()).showWinner(anyString());
clickAndAssertValueAt(0,2, "X");
verify(view).showWinner("X");
}
private void clickAndAssertValueAt(int row, int col, String expectedValue) {
presenter.onButtonSelected(row, col);
verify(view).setButtonText(row, col, expectedValue);
}
}
21. MVVM + Data Binding
• Model
Data + State + Business logic
• View
Binds to observable variables and actions exposed by the ViewModel
• ViewModel
Responsible for wrapping the model and preparing observable data needed by the view. It also provides
hooks for the view to pass events to the model
22. MVVM - Model
public class Board {
private Cell[][] cells = new Cell[3][3];
private Player winner;
private GameState state;
private Player currentTurn;
private enum GameState { IN_PROGRESS, FINISHED };
public Board() {...}
/**
* Restart or start a new game, will clear the board and win status
*/
public void restart() {...}
/**
* Mark the current row for the player who's current turn it is.
* Will perform no-op if the arguments are out of range or if that position is already played.
* Will also perform a no-op if the game is already over.
*
* @param row 0..2
* @param col 0..2
* @return the player that moved or null if we did not move anything.
*
*/
public Player mark( int row, int col ) {...}
public Player getWinner() {...}
private void clearCells() {...}
private void flipCurrentTurn() {...}
private boolean isValid(int row, int col ) {...}
private boolean isOutOfBounds(int idx){...}
private boolean isCellValueAlreadySet(int row, int col) {...}
private boolean isWinningMoveByPlayer(Player player, int currentRow, int currentCol) {...}
}
public class Cell {
private Player value;
public Player getValue() { return value; }
public void setValue(Player value) { this.value = value; }
}
public enum Player { X , O }
24. MVVM - ViewModel
public class TicTacToeActivity extends AppCompatActivity {
TicTacToeViewModel viewModel = new TicTacToeViewModel();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TictactoeBinding binding =
DataBindingUtil.setContentView(this, R.layout.tictactoe);
binding.setViewModel(viewModel);
viewModel.onCreate();
}
@Override
protected void onPause() {
super.onPause();
viewModel.onPause();
}
@Override
protected void onResume() {
super.onResume();
viewModel.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
viewModel.onDestroy();
}
}
View
public class TicTacToeViewModel extends ViewModel {
private Board model;
public final ObservableArrayMap<String, String> cells = new ObservableArrayMap<>();
public final ObservableField<String> winner = new ObservableField<>();
public TicTacToeViewModel() {
model = new Board();
}
public void onResetSelected() {
model.restart();
winner.set(null);
cells.clear();
}
public void onClickedCellAt(int row, int col) {
Player playerThatMoved = model.mark(row, col);
cells.put(…, …);
winner.set(…);
}
}
ViewModel
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable name="viewModel" type="com.acme.tictactoe.viewmodel.TicTacToeViewModel" />
</data>
...
<Button
style="@style/tictactoebutton"
android:onClick="@{() -> viewModel.onClickedCellAt(0,0)}"
android:text='@{viewModel.cells["00"]}' />
...
</layout>
Observing value of cells at key “00”
Invoke onClickedCellAt(0,0) on click
25. MVVM - Testing ViewModel
public class TicTacToeViewModelTests {
private TicTacToeViewModel viewModel;
@Before
public void setup() {
viewModel = new TicTacToeViewModel();
}
private void clickAndAssertValueAt(int row, int col, String expectedValue) {
viewModel.onClickedCellAt(row, col);
assertEquals(expectedValue, viewModel.cells.get("" + row + col));
}
/**
* This test will simulate and verify x is the winner.
*
* X | X | X
* O | |
* | O |
*/
@Test
public void test3inRowAcrossTopForX() {
clickAndAssertValueAt(0,0, "X");
assertNull(viewModel.winner.get());
clickAndAssertValueAt(1,0, "O");
assertNull(viewModel.winner.get());
clickAndAssertValueAt(0,1, "X");
assertNull(viewModel.winner.get());
clickAndAssertValueAt(2,1, "O");
assertNull(viewModel.winner.get());
clickAndAssertValueAt(0,2, "X");
assertEquals("X", viewModel.winner.get());
}
}
26. Checkpoint
• Understand the principles and components of
• MVC
• MVP
• MVVM
• Reactive / Uni-Directional
• Google Architecture Components
27. Concerns w/ MV[x]
2 States to maintain
Recreation from Big Nerd Ranch
Android Programming 3rd Edition
Chapter 2 [Android and MVC pg 38],
Controller littered with code to handle this
(across threads)
42. ViewModel
em@realm.io
public class MyActivity extends LifecycleActivity {
private MyViewModel mViewModel;
private TextView meTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
meTextView = (TextView) findViewById(R.id.books_tv);
// Android will instantiate my ViewModel, and the best part is
// the viewModel will survive configurationChanges!
mViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
}
}
https://developer.android.com/topic/libraries/architecture/viewmodel.html#the_lifecycle_of_a_viewmodel
43. ViewModel
em@realm.io
public class MyActivity extends AppCompatActivity {
private MyViewModel mViewModel;
private TextView meTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
meTextView = (TextView) findViewById(R.id.books_tv);
// Android will instantiate my ViewModel, and the best part is
// the viewModel will survive configurationChanges!
mViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
}
}
https://developer.android.com/topic/libraries/architecture/viewmodel.html#the_lifecycle_of_a_viewmodel
44. ViewModel
em@realm.io
public class MyViewModel extends ViewModel {
private Realm mDb;
public MyViewModel() {
// Initialization in construction
}
@Override
protected void onCleared() {
mDb.close();
}
}
public class MyViewModel extends AndroidViewModel {
private Realm mDb;
public MyViewModel(Application application) {
super(application);
}
@Override
protected void onCleared() {
mDb.close();
getApplication();
// Do something with the application
}
}
ViewModel Android ViewModel
64. em@realm.io
Room
public class LoanWithUserAndBook {
public String id;
@ColumnInfo(name="title")
public String bookTitle;
@ColumnInfo(name="name")
public String userName;
@TypeConverters(DateConverter.class)
public Date startTime;
@TypeConverters(DateConverter.class)
public Date endTime;
}
65. em@realm.io
Room
@Entity(foreignKeys = {
@ForeignKey(entity = Book.class,
parentColumns = "id",
childColumns = "book_id"),
@ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "user_id")})
@TypeConverters(DateConverter.class)
public class Loan {
public @PrimaryKey String id;
public Date startTime;
public Date endTime;
@ColumnInfo(name="book_id")
public String bookId;
@ColumnInfo(name="user_id")
public String userId;
}
Define Entites
@Entity
public class User {
public @PrimaryKey String id;
public String name;
public String lastName;
public int age;
}
@Entity
public class Book {
public @PrimaryKey String id;
public String title;
}
66. em@realm.io
Room
@Database(entities = {User.class, Book.class, Loan.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase INSTANCE;
public abstract UserDao userModel();
public abstract BookDao bookModel();
public abstract LoanDao loanModel();
public static AppDatabase getInMemoryDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE =
Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "LibraryDb")
.build();
}
return INSTANCE;
}
public static void destroyInstance() {
INSTANCE = null;
}
}
Define Database
67. em@realm.io
Room
@Database(entities = {User.class, Book.class, Loan.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase INSTANCE;
public abstract UserDao userModel();
public abstract BookDao bookModel();
public abstract LoanDao loanModel();
public static AppDatabase getInMemoryDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE =
Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "LibraryDb")
.build();
}
return INSTANCE;
}
public static void destroyInstance() {
INSTANCE = null;
}
}
Define Database
68. em@realm.io
Room
@Dao
@TypeConverters(DateConverter.class)
public interface LoanDao {
@Query("SELECT * From Loan")
LiveData<List<Loan>> findAll();
@Query("SELECT Loan.id, Book.title, User.name, Loan.startTime, Loan.endTime From Loan " +
"INNER JOIN Book ON Loan.book_id = Book.id " +
"INNER JOIN User ON Loan.user_id = User.id ")
LiveData<List<LoanWithUserAndBook>> findAllWithUserAndBook();
@Query("SELECT Loan.id, Book.title as title, User.name as name, Loan.startTime, Loan.endTime " +
"FROM Book " +
"INNER JOIN Loan ON Loan.book_id = Book.id " +
"INNER JOIN User on User.id = Loan.user_id " +
"WHERE User.name LIKE :userName " +
"AND Loan.endTime > :after "
)
public LiveData<List<LoanWithUserAndBook>> findLoansByNameAfter(String userName, Date after);
@Insert(onConflict = ABORT)
void insertLoan(Loan loan);
@Query("DELETE FROM Loan")
void deleteAll();
}
Define DAO Interface
69. em@realm.io
Room
public class CustomResultViewModel extends AndroidViewModel {
private AppDatabase mDb;
public CustomResultViewModel(Application application) {
super(application);
mDb = AppDatabase.getDatabase(application);
}
public logMikesLoans() {
LiveData<List<LoanWithUserAndBook>> loans =
mDb.loanModel().findLoansByNameAfter("Mike", getYesterdayDate());
...
}
Using Room DAOs
70. em@realm.io
Room
@RunWith(AndroidJUnit4.class)
public class SimpleEntityReadWriteTest {
private UserDao mUserDao;
private TestDatabase mDb;
@Before
public void createDb() {
Context context = InstrumentationRegistry.getTargetContext();
mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
mUserDao = mDb.getUserDao();
}
@After
public void closeDb() throws IOException {
mDb.close();
}
@Test
public void writeUserAndReadInList() throws Exception {
User user = TestUtil.createUser(3);
user.setName("george");
mUserDao.insert(user);
List<User> byName = mUserDao.findUsersByName("george");
assertThat(byName.get(0), equalTo(user));
}
}
Room Test Support
https://developer.android.com/topic/libraries/architecture/room.html#testing-android
72. Architecture
“It is impossible to have one way of writing apps that will be the
best for every scenario.
If you already have a good way of writing Android apps, you don't
need to change.
That being said, this recommended architecture should be a good
starting point for most use cases.”
https://developer.android.com/topic/libraries/architecture/guide.html#recommended_app_architecture
73. Official Android Architecture Dogma Guidance
For more information see: https://developer.android.com/topic/libraries/architecture/guide.html
74. General Good Practices
• Avoid storing state or data in Activities, Fragments, Services, Broadcast Receivers
• Keep a strong separation of concerns
• Build your Data, Model, ViewModel, Activity/Fragment, etc. as components with strong inputs / outputs
• Android Architecture Components and libraries. Focus on what makes your app unique
• Build your apps UI/UX to function well when offline
• Use Service Locators or Dependency Injection for dependencies
75. Additional Materials
• Android Architecture Components
• https://developer.android.com/topic/libraries/architecture/index.html
• https://github.com/googlesamples/android-architecture-components
• https://github.com/googlesamples/android-architecture (Architecture Blueprints)
• Other Reactive Uni-Directional Architectures
• Model View Intent - http://hannesdorfmann.com/android/mosby3-mvi-2#model-view-intent-mvi
• FLUX on Android - https://github.com/lgvalle/android-flux-todo-app
• Other Articles I’ve published on these topics
• https://news.realm.io/news/eric-maxwell-mvc-mvp-and-mvvm-on-android/
• https://news.realm.io/news/eric-maxwell-uni-directional-architecture-android-using-realm
• https://news.realm.io/news/android-architecture-components-and-realm/
• Samples
• https://github.com/ericmaxwell2003/android-architecture-samples