SlideShare a Scribd company logo
1 of 76
Download to read offline
Modern Android Architecture
Eric Maxwell

Product Engineer @ Realm

Android & iOS Developer
Columbus Kotlin User Group Organizer
Architecture
MVC
MVP
MVVM
Uni-Directional
Reactive
Architecture Components
Architecture
I’m untestable
Nobody understands me!
I’m unmaintainable :-(
Architecture
I can be tested
New Developers know exactly
where all my parts are and
how to update me!!
I’m composable!
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
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
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 }
MVC - View
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("");
}
}
}
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() {...}
}
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("");
}
}
}
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
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!
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 }
MVP - View
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("");
}
}
}
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
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
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
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);
}
}
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
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 }
MVVM - View
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
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());
}
}
Checkpoint
• Understand the principles and components of
• MVC
• MVP
• MVVM
• Reactive / Uni-Directional
• Google Architecture Components
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)
Reactive Architecture
https://en.wikipedia.org/wiki/Model-view-controller
Uni Directional
Examples
• Flux/Redux
• Model View Intent
• Realm
• Android Architecture Components
• Uni-Directional Framework Post covering first 3

http://bit.ly/2temW62
Characteristics
• Single State
• Data and interactions flow in 1 direction
• Reactive
• Asynchronous by nature
Reactive Architecture
https://en.wikipedia.org/wiki/Model-view-controller
Uni Directional
Examples
• Flux/Redux
• Model View Intent
• Realm
• Android Architecture Components
• Uni-Directional Framework Post covering first 3

http://bit.ly/2temW62
Characteristics
• Single State
• Data and interactions flow in 1 direction
• Reactive
• Asynchronous by nature
em@realm.io
https://developer.android.com/topic/libraries/architecture/index.html
Room
ViewModel
Lifecycle
LiveData
em@realm.io
Android Architecture Components
Modern Reactive Architecture
Room
ViewModel
Lifecycle
LiveData
em@realm.io
Android Architecture Components
Modern Reactive Architecture
Lifecycle
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
public void onCreate(...) {
myLocationListener = new
MyLocationListener(this, (Message location) -> {
// update UI
});
}
public void onStart() {
super.onStart();
myLocationListener.start();
}
public void onStop() {
super.onStop();
myLocationListener.stop();
}
}
class MyLocationListener {
public MyLocationListener(
Context context, Callback callback) {
// ...
}
void start() {
// connect to system location service
}
void stop() {
// disconnect from system location service
}
}
Activity managing all the components
Lifecycle
Owner Component
Lifecycle Aware Components
Lifecycle
Owner
Component is aware of owners lifecycle
class CustomResultUserActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
public void onCreate(...) {
myLocationListener = new MyLocationListener(
this, getLifecycle(), location -> {
// update UI
});
Util.checkUserStatus(result -> {
if (result) {
myLocationListener.enable();
}
});
}
}
Lifecycle
Component is aware of owners lifecycle
class MyLocationListener implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
private void start() {
if (enabled) {
// connect
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private void stop() {
/* disconnect if connected */
}
public void enable() {
if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
/* connect if not connected */
}
}
Component
Lifecycle
Lifecycle Aware Components
Room
ViewModel
Lifecycle
LiveData
em@realm.io
Android Architecture Components
Modern Reactive Architecture
ViewModel
em@realm.io
Configuration Changes Destroys Activities
ViewModel
em@realm.io
ViewModels
Survive Rotation
ViewModel
em@realm.iohttps://developer.android.com/topic/libraries/architecture/viewmodel.html#the_lifecycle_of_a_viewmodel
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
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
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
Room
ViewModel
Lifecycle
LiveData
em@realm.io
Android Architecture Components
Modern Reactive Architecture
LiveData
em@realm.io
• An observable value container
• Lifecycle Aware
• Any data can be represented as LiveData
LiveData
em@realm.io
• LiveData<String>
• LiveData<List<Dog>>
• CustomLiveData<Dog>
LiveData Transformations
em@realm.io
LiveData<List<LoanWithUserAndBook>> loanLiveData = mDb.loanModel().findLoansByNameAfter("Mike", getYesterdayDate());
// Instead of exposing the list of Loans, we can apply a transformation and expose Strings.
LiveData<String> stringLiveData = Transformations.map(loanLiveData, new Function<List<LoanWithUserAndBook>, String>() {
@Override
public String apply(List<LoanWithUserAndBook> loansWithUserAndBook) {
StringBuilder sb = new StringBuilder();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm",
Locale.US);
for (LoanWithUserAndBook loan : loansWithUserAndBook) {
sb.append(String.format("%sn (Returned: %s)n",
loan.bookTitle,
simpleDateFormat.format(loan.endTime)));
}
return sb.toString();
}
});
Map
LiveData Transformations
em@realm.io
LiveData<List<LoanWithUserAndBook>> loanLiveData = mDb.loanModel().findLoansByNameAfter("Mike", getYesterdayDate());
// Instead of exposing the list of Loans, we can apply a transformation and expose Strings.
LiveData<String> stringLiveData = Transformations.map(loanLiveData, new Function<List<LoanWithUserAndBook>, String>() {
@Override
public String apply(List<LoanWithUserAndBook> loansWithUserAndBook) {
StringBuilder sb = new StringBuilder();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm",
Locale.US);
for (LoanWithUserAndBook loan : loansWithUserAndBook) {
sb.append(String.format("%sn (Returned: %s)n",
loan.bookTitle,
simpleDateFormat.format(loan.endTime)));
}
return sb.toString();
}
});
Map
LiveData Transformations
em@realm.io
LiveData<List<LoanWithUserAndBook>> loanLiveData = mDb.loanModel().findLoansByNameAfter("Mike", getYesterdayDate());
// Instead of exposing the list of Loans, we can apply a transformation and expose Strings.
LiveData<String> stringLiveData = Transformations.map(loanLiveData, new Function<List<LoanWithUserAndBook>, String>() {
@Override
public String apply(List<LoanWithUserAndBook> loansWithUserAndBook) {
StringBuilder sb = new StringBuilder();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm",
Locale.US);
for (LoanWithUserAndBook loan : loansWithUserAndBook) {
sb.append(String.format("%sn (Returned: %s)n",
loan.bookTitle,
simpleDateFormat.format(loan.endTime)));
}
return sb.toString();
}
});
Map
LiveData Transformations
em@realm.io
LiveData<List<LoanWithUserAndBook>> loanLiveData = mDb.loanModel().findLoansByNameAfter("Mike", getYesterdayDate());
// Instead of exposing the list of Loans, we can apply a transformation and expose Strings.
LiveData<String> stringLiveData = Transformations.map(loanLiveData, new Function<List<LoanWithUserAndBook>, String>() {
@Override
public String apply(List<LoanWithUserAndBook> loansWithUserAndBook) {
StringBuilder sb = new StringBuilder();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm",
Locale.US);
for (LoanWithUserAndBook loan : loansWithUserAndBook) {
sb.append(String.format("%sn (Returned: %s)n",
loan.bookTitle,
simpleDateFormat.format(loan.endTime)));
}
return sb.toString();
}
});
Map
LiveData Transformations
em@realm.io
LiveData<List<LoanWithUserAndBook>> loanLiveData = mDb.loanModel().findLoansByNameAfter("Mike", getYesterdayDate());
// Instead of exposing the list of Loans, we can apply a transformation and expose Strings.
LiveData<String> stringLiveData = Transformations.map(loanLiveData, new Function<List<LoanWithUserAndBook>, String>() {
@Override
public String apply(List<LoanWithUserAndBook> loansWithUserAndBook) {
StringBuilder sb = new StringBuilder();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm",
Locale.US);
for (LoanWithUserAndBook loan : loansWithUserAndBook) {
sb.append(String.format("%sn (Returned: %s)n",
loan.bookTitle,
simpleDateFormat.format(loan.endTime)));
}
return sb.toString();
}
});
Map
LiveData
em@realm.io
Subscribing to LiveData
// Here an Activity, Subscribed to LiveData

public class CustomResultUserActivity extends AppCompatActivity {
private CustomResultViewModel mShowUserViewModel;
private TextView mBooksTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.db_activity);
mBooksTextView = (TextView) findViewById(R.id.books_tv);
mShowUserViewModel = ViewModelProviders.of(this).get(CustomResultViewModel.class);
mShowUserViewModel.getLoansResult().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable final String result) {
mBooksTextView.setText(result);
}
});
}
}
LiveData
em@realm.io
Subscribing to LiveData
// Here an Activity, Subscribed to LiveData

public class CustomResultUserActivity extends AppCompatActivity {
private CustomResultViewModel mShowUserViewModel;
private TextView mBooksTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.db_activity);
mBooksTextView = (TextView) findViewById(R.id.books_tv);
mShowUserViewModel = ViewModelProviders.of(this).get(CustomResultViewModel.class);
mShowUserViewModel.getLoansResult().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable final String result) {
mBooksTextView.setText(result);
}
});
}
}
LiveData respects
lifecycle of
LiveData
em@realm.io
public class RealmLiveData<T extends RealmModel> extends LiveData<RealmResults<T>> {
private RealmResults<T> mResults;
private RealmChangeListener<RealmResults<T>> listener = new RealmChangeListener<RealmResults<T>>() {
@Override
public void onChange(RealmResults<T> results) {
setValue(results);
}
};
public RealmLiveData(RealmResults<T> mResults) {
this.mResults = mResults;
}
@Override
protected void onActive() {
mResults.addChangeListener(listener);
}
@Override
protected void onInactive() {
mResults.removeChangeListener(listener);
}
}
Extensible - Represent anything as LiveData
LiveData
em@realm.io
public class RealmLiveData<T extends RealmModel> extends LiveData<RealmResults<T>> {
private RealmResults<T> mResults;
private RealmChangeListener<RealmResults<T>> listener = new RealmChangeListener<RealmResults<T>>() {
@Override
public void onChange(RealmResults<T> results) {
setValue(results);
}
};
public RealmLiveData(RealmResults<T> mResults) {
this.mResults = mResults;
}
@Override
protected void onActive() {
mResults.addChangeListener(listener);
}
@Override
protected void onInactive() {
mResults.removeChangeListener(listener);
}
}
LiveData
em@realm.io
public class RealmLiveData<T extends RealmModel> extends LiveData<RealmResults<T>> {
private RealmResults<T> mResults;
private RealmChangeListener<RealmResults<T>> listener = new RealmChangeListener<RealmResults<T>>() {
@Override
public void onChange(RealmResults<T> results) {
setValue(results);
}
};
public RealmLiveData(RealmResults<T> mResults) {
this.mResults = mResults;
}
@Override
protected void onActive() {
mResults.addChangeListener(listener);
}
@Override
protected void onInactive() {
mResults.removeChangeListener(listener);
}
}
LiveData
em@realm.io
// Use Custom LiveData

public RealmLiveData<Loan> findLoansByNameAfter(final String userName, final Date after) {
return new RealmLiveData<>(mRealm.where(Loan.class)
.like("user.name", userName)
.greaterThan("endTime", after)
.findAllAsync());
}
// Can even transform that live data
RealmLiveData<Loan> loans = findLoansByNameAfter(“Mike”, getYesterdayDate());
LiveData<String> mLoansResult = Transformations.map(loans, new Function<RealmResults<Loan>, String>() {
@Override
public String apply(RealmResults<Loan> loans) {
StringBuilder sb = new StringBuilder();
for (Loan loan : loans) {
sb.append(String.format("%sn (Returned: %s)n",
loan.getBook().getTitle(),
simpleDateFormat.format(loan.getEndTime())));
}
return sb.toString();
}
});
LiveData
em@realm.io
// Use Custom LiveData

public RealmLiveData<Loan> findLoansByNameAfter(final String userName, final Date after) {
return new RealmLiveData<>(mRealm.where(Loan.class)
.like("user.name", userName)
.greaterThan("endTime", after)
.findAllAsync());
}
// Can even transform that live data
RealmLiveData<Loan> loans = findLoansByNameAfter(“Mike”, getYesterdayDate());
LiveData<String> mLoansResult = Transformations.map(loans, new Function<RealmResults<Loan>, String>() {
@Override
public String apply(RealmResults<Loan> loans) {
StringBuilder sb = new StringBuilder();
for (Loan loan : loans) {
sb.append(String.format("%sn (Returned: %s)n",
loan.getBook().getTitle(),
simpleDateFormat.format(loan.getEndTime())));
}
return sb.toString();
}
});
LiveData
em@realm.io
// Use Custom LiveData

public RealmLiveData<Loan> findLoansByNameAfter(final String userName, final Date after) {
return new RealmLiveData<>(mRealm.where(Loan.class)
.like("user.name", userName)
.greaterThan("endTime", after)
.findAllAsync());
}
// Can even transform that live data
RealmLiveData<Loan> loans = findLoansByNameAfter(“Mike”, getYesterdayDate());
LiveData<String> mLoansResult = Transformations.map(loans, new Function<RealmResults<Loan>, String>() {
@Override
public String apply(RealmResults<Loan> loans) {
StringBuilder sb = new StringBuilder();
for (Loan loan : loans) {
sb.append(String.format("%sn (Returned: %s)n",
loan.getBook().getTitle(),
simpleDateFormat.format(loan.getEndTime())));
}
return sb.toString();
}
});
Room
ViewModel
Lifecycle
LiveData
em@realm.io
Android Architecture Components
Modern Reactive Architecture
em@realm.io
Room
• ORM for SQLite
• DAOs defined in interface are generated at compile time
• Results are Live(Data)
em@realm.io
Room
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;
}
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;
}
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
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
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
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
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
em@realm.io
Android Architecture Components
In a Nutshell
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
Official Android Architecture Dogma Guidance
For more information see: https://developer.android.com/topic/libraries/architecture/guide.html
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
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
Eric Maxwell
em@realm.io
www.realm.io
@emmax
Thank you!

More Related Content

What's hot

DevOps without DevOps Tools
DevOps without DevOps ToolsDevOps without DevOps Tools
DevOps without DevOps ToolsJagatveer Singh
 
Gateway/APIC security
Gateway/APIC securityGateway/APIC security
Gateway/APIC securityShiu-Fun Poon
 
Microserviços: uma introdução
Microserviços: uma introduçãoMicroserviços: uma introdução
Microserviços: uma introduçãoDaniel Baptista Dias
 
API Governance and GitOps in Hybrid Integration Platform (MuleSoft)
API Governance and GitOps in Hybrid Integration Platform (MuleSoft)API Governance and GitOps in Hybrid Integration Platform (MuleSoft)
API Governance and GitOps in Hybrid Integration Platform (MuleSoft)Sumanth Donthi
 
Open API and API Management - Introduction and Comparison of Products: TIBCO ...
Open API and API Management - Introduction and Comparison of Products: TIBCO ...Open API and API Management - Introduction and Comparison of Products: TIBCO ...
Open API and API Management - Introduction and Comparison of Products: TIBCO ...Kai Wähner
 
Introduction to Modern Identity with Auth0's Developer
 Introduction to Modern Identity with Auth0's Developer Introduction to Modern Identity with Auth0's Developer
Introduction to Modern Identity with Auth0's DeveloperProduct School
 
DevOps & DevSecOps in Swiss Banking
DevOps & DevSecOps in Swiss BankingDevOps & DevSecOps in Swiss Banking
DevOps & DevSecOps in Swiss BankingAarno Aukia
 

What's hot (9)

DevOps without DevOps Tools
DevOps without DevOps ToolsDevOps without DevOps Tools
DevOps without DevOps Tools
 
DevSecOps Jenkins Pipeline -Security
DevSecOps Jenkins Pipeline -SecurityDevSecOps Jenkins Pipeline -Security
DevSecOps Jenkins Pipeline -Security
 
Gateway/APIC security
Gateway/APIC securityGateway/APIC security
Gateway/APIC security
 
Microserviços: uma introdução
Microserviços: uma introduçãoMicroserviços: uma introdução
Microserviços: uma introdução
 
API Governance and GitOps in Hybrid Integration Platform (MuleSoft)
API Governance and GitOps in Hybrid Integration Platform (MuleSoft)API Governance and GitOps in Hybrid Integration Platform (MuleSoft)
API Governance and GitOps in Hybrid Integration Platform (MuleSoft)
 
FAPI 最新情報 - OpenID BizDay #15
FAPI 最新情報 - OpenID BizDay #15FAPI 最新情報 - OpenID BizDay #15
FAPI 最新情報 - OpenID BizDay #15
 
Open API and API Management - Introduction and Comparison of Products: TIBCO ...
Open API and API Management - Introduction and Comparison of Products: TIBCO ...Open API and API Management - Introduction and Comparison of Products: TIBCO ...
Open API and API Management - Introduction and Comparison of Products: TIBCO ...
 
Introduction to Modern Identity with Auth0's Developer
 Introduction to Modern Identity with Auth0's Developer Introduction to Modern Identity with Auth0's Developer
Introduction to Modern Identity with Auth0's Developer
 
DevOps & DevSecOps in Swiss Banking
DevOps & DevSecOps in Swiss BankingDevOps & DevSecOps in Swiss Banking
DevOps & DevSecOps in Swiss Banking
 

Similar to Modern Android Architecture

Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019UA Mobile
 
Presentation Android Architecture Components
Presentation Android Architecture ComponentsPresentation Android Architecture Components
Presentation Android Architecture ComponentsAttract Group
 
Net conf BG xamarin lecture
Net conf BG xamarin lectureNet conf BG xamarin lecture
Net conf BG xamarin lectureTsvyatko Konov
 
Does testability imply good design - Andrzej Jóźwiak - TomTom Dev Day 2022
Does testability imply good design - Andrzej Jóźwiak - TomTom Dev Day 2022Does testability imply good design - Andrzej Jóźwiak - TomTom Dev Day 2022
Does testability imply good design - Andrzej Jóźwiak - TomTom Dev Day 2022Andrzej Jóźwiak
 
[22]Efficient and Testable MVVM pattern
[22]Efficient and Testable MVVM pattern[22]Efficient and Testable MVVM pattern
[22]Efficient and Testable MVVM patternNAVER Engineering
 
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...DroidConTLV
 
Useful Tools for Making Video Games - XNA (2008)
Useful Tools for Making Video Games - XNA (2008)Useful Tools for Making Video Games - XNA (2008)
Useful Tools for Making Video Games - XNA (2008)Korhan Bircan
 
Model View Intent on Android
Model View Intent on AndroidModel View Intent on Android
Model View Intent on AndroidCody Engel
 
Применение шаблона проектирования MVVM при разработке архитектуры Windows Pho...
Применение шаблона проектирования MVVM при разработке архитектуры Windows Pho...Применение шаблона проектирования MVVM при разработке архитектуры Windows Pho...
Применение шаблона проектирования MVVM при разработке архитектуры Windows Pho...Nikolay Rumyantsev
 
준비하세요 Angular js 2.0
준비하세요 Angular js 2.0준비하세요 Angular js 2.0
준비하세요 Angular js 2.0Jeado Ko
 
My way to clean android v2 English DroidCon Spain
My way to clean android v2 English DroidCon SpainMy way to clean android v2 English DroidCon Spain
My way to clean android v2 English DroidCon SpainChristian Panadero
 
Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014
Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014
Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014First Tuesday Bergen
 
Android and the Seven Dwarfs from Devox'15
Android and the Seven Dwarfs from Devox'15Android and the Seven Dwarfs from Devox'15
Android and the Seven Dwarfs from Devox'15Murat Yener
 
Android Design Patterns
Android Design PatternsAndroid Design Patterns
Android Design PatternsGodfrey Nolan
 
Synchronizing without internet - Multipeer Connectivity (iOS)
Synchronizing without internet - Multipeer Connectivity (iOS)Synchronizing without internet - Multipeer Connectivity (iOS)
Synchronizing without internet - Multipeer Connectivity (iOS)Jorge Maroto
 
Practical Model View Programming (Roadshow Version)
Practical Model View Programming (Roadshow Version)Practical Model View Programming (Roadshow Version)
Practical Model View Programming (Roadshow Version)Marius Bugge Monsen
 
Tilting at Windmills with ctypes and cygwinreg
Tilting at Windmills with ctypes and cygwinregTilting at Windmills with ctypes and cygwinreg
Tilting at Windmills with ctypes and cygwinregSimon Law
 

Similar to Modern Android Architecture (20)

Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019Working effectively with ViewModels and TDD - UA Mobile 2019
Working effectively with ViewModels and TDD - UA Mobile 2019
 
Presentation Android Architecture Components
Presentation Android Architecture ComponentsPresentation Android Architecture Components
Presentation Android Architecture Components
 
Net conf BG xamarin lecture
Net conf BG xamarin lectureNet conf BG xamarin lecture
Net conf BG xamarin lecture
 
Does testability imply good design - Andrzej Jóźwiak - TomTom Dev Day 2022
Does testability imply good design - Andrzej Jóźwiak - TomTom Dev Day 2022Does testability imply good design - Andrzej Jóźwiak - TomTom Dev Day 2022
Does testability imply good design - Andrzej Jóźwiak - TomTom Dev Day 2022
 
[22]Efficient and Testable MVVM pattern
[22]Efficient and Testable MVVM pattern[22]Efficient and Testable MVVM pattern
[22]Efficient and Testable MVVM pattern
 
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
 
Griffon @ Svwjug
Griffon @ SvwjugGriffon @ Svwjug
Griffon @ Svwjug
 
Useful Tools for Making Video Games - XNA (2008)
Useful Tools for Making Video Games - XNA (2008)Useful Tools for Making Video Games - XNA (2008)
Useful Tools for Making Video Games - XNA (2008)
 
Model View Intent on Android
Model View Intent on AndroidModel View Intent on Android
Model View Intent on Android
 
Применение шаблона проектирования MVVM при разработке архитектуры Windows Pho...
Применение шаблона проектирования MVVM при разработке архитектуры Windows Pho...Применение шаблона проектирования MVVM при разработке архитектуры Windows Pho...
Применение шаблона проектирования MVVM при разработке архитектуры Windows Pho...
 
준비하세요 Angular js 2.0
준비하세요 Angular js 2.0준비하세요 Angular js 2.0
준비하세요 Angular js 2.0
 
My way to clean android v2 English DroidCon Spain
My way to clean android v2 English DroidCon SpainMy way to clean android v2 English DroidCon Spain
My way to clean android v2 English DroidCon Spain
 
Dependency Injection for Android
Dependency Injection for AndroidDependency Injection for Android
Dependency Injection for Android
 
Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014
Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014
Dependency Injection for Android @ Ciklum speakers corner Kiev 29. May 2014
 
Android and the Seven Dwarfs from Devox'15
Android and the Seven Dwarfs from Devox'15Android and the Seven Dwarfs from Devox'15
Android and the Seven Dwarfs from Devox'15
 
My way to clean android V2
My way to clean android V2My way to clean android V2
My way to clean android V2
 
Android Design Patterns
Android Design PatternsAndroid Design Patterns
Android Design Patterns
 
Synchronizing without internet - Multipeer Connectivity (iOS)
Synchronizing without internet - Multipeer Connectivity (iOS)Synchronizing without internet - Multipeer Connectivity (iOS)
Synchronizing without internet - Multipeer Connectivity (iOS)
 
Practical Model View Programming (Roadshow Version)
Practical Model View Programming (Roadshow Version)Practical Model View Programming (Roadshow Version)
Practical Model View Programming (Roadshow Version)
 
Tilting at Windmills with ctypes and cygwinreg
Tilting at Windmills with ctypes and cygwinregTilting at Windmills with ctypes and cygwinreg
Tilting at Windmills with ctypes and cygwinreg
 

Recently uploaded

Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...soniya singh
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureDinusha Kumarasiri
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWave PLM
 
React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaHanief Utama
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
 
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样umasea
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio, Inc.
 
MYjobs Presentation Django-based project
MYjobs Presentation Django-based projectMYjobs Presentation Django-based project
MYjobs Presentation Django-based projectAnoyGreter
 
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfGOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfAlina Yurenko
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptkotipi9215
 
Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - InfographicHr365.us smith
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)jennyeacort
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEEVICTOR MAESTRE RAMIREZ
 
The Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfThe Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfPower Karaoke
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantAxelRicardoTrocheRiq
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 

Recently uploaded (20)

Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with Azure
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need It
 
React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief Utama
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
 
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
 
MYjobs Presentation Django-based project
MYjobs Presentation Django-based projectMYjobs Presentation Django-based project
MYjobs Presentation Django-based project
 
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfGOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.ppt
 
Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - Infographic
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
 
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort ServiceHot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEE
 
The Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfThe Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdf
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service Consultant
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 

Modern Android Architecture

  • 1. Modern Android Architecture Eric Maxwell
 Product Engineer @ Realm
 Android & iOS Developer Columbus Kotlin User Group Organizer
  • 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)
  • 28. Reactive Architecture https://en.wikipedia.org/wiki/Model-view-controller Uni Directional Examples • Flux/Redux • Model View Intent • Realm • Android Architecture Components • Uni-Directional Framework Post covering first 3
 http://bit.ly/2temW62 Characteristics • Single State • Data and interactions flow in 1 direction • Reactive • Asynchronous by nature
  • 29. Reactive Architecture https://en.wikipedia.org/wiki/Model-view-controller Uni Directional Examples • Flux/Redux • Model View Intent • Realm • Android Architecture Components • Uni-Directional Framework Post covering first 3
 http://bit.ly/2temW62 Characteristics • Single State • Data and interactions flow in 1 direction • Reactive • Asynchronous by nature
  • 33. Lifecycle class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, (Message location) -> { // update UI }); } public void onStart() { super.onStart(); myLocationListener.start(); } public void onStop() { super.onStop(); myLocationListener.stop(); } } class MyLocationListener { public MyLocationListener( Context context, Callback callback) { // ... } void start() { // connect to system location service } void stop() { // disconnect from system location service } } Activity managing all the components
  • 35. Lifecycle Owner Component is aware of owners lifecycle class CustomResultUserActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener( this, getLifecycle(), location -> { // update UI }); Util.checkUserStatus(result -> { if (result) { myLocationListener.enable(); } }); } }
  • 36. Lifecycle Component is aware of owners lifecycle class MyLocationListener implements LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_START) private void start() { if (enabled) { // connect } } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) private void stop() { /* disconnect if connected */ } public void enable() { if (lifecycle.getCurrentState().isAtLeast(STARTED)) { /* connect if not connected */ } } Component
  • 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
  • 46. LiveData em@realm.io • An observable value container • Lifecycle Aware • Any data can be represented as LiveData
  • 48. LiveData Transformations em@realm.io LiveData<List<LoanWithUserAndBook>> loanLiveData = mDb.loanModel().findLoansByNameAfter("Mike", getYesterdayDate()); // Instead of exposing the list of Loans, we can apply a transformation and expose Strings. LiveData<String> stringLiveData = Transformations.map(loanLiveData, new Function<List<LoanWithUserAndBook>, String>() { @Override public String apply(List<LoanWithUserAndBook> loansWithUserAndBook) { StringBuilder sb = new StringBuilder(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US); for (LoanWithUserAndBook loan : loansWithUserAndBook) { sb.append(String.format("%sn (Returned: %s)n", loan.bookTitle, simpleDateFormat.format(loan.endTime))); } return sb.toString(); } }); Map
  • 49. LiveData Transformations em@realm.io LiveData<List<LoanWithUserAndBook>> loanLiveData = mDb.loanModel().findLoansByNameAfter("Mike", getYesterdayDate()); // Instead of exposing the list of Loans, we can apply a transformation and expose Strings. LiveData<String> stringLiveData = Transformations.map(loanLiveData, new Function<List<LoanWithUserAndBook>, String>() { @Override public String apply(List<LoanWithUserAndBook> loansWithUserAndBook) { StringBuilder sb = new StringBuilder(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US); for (LoanWithUserAndBook loan : loansWithUserAndBook) { sb.append(String.format("%sn (Returned: %s)n", loan.bookTitle, simpleDateFormat.format(loan.endTime))); } return sb.toString(); } }); Map
  • 50. LiveData Transformations em@realm.io LiveData<List<LoanWithUserAndBook>> loanLiveData = mDb.loanModel().findLoansByNameAfter("Mike", getYesterdayDate()); // Instead of exposing the list of Loans, we can apply a transformation and expose Strings. LiveData<String> stringLiveData = Transformations.map(loanLiveData, new Function<List<LoanWithUserAndBook>, String>() { @Override public String apply(List<LoanWithUserAndBook> loansWithUserAndBook) { StringBuilder sb = new StringBuilder(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US); for (LoanWithUserAndBook loan : loansWithUserAndBook) { sb.append(String.format("%sn (Returned: %s)n", loan.bookTitle, simpleDateFormat.format(loan.endTime))); } return sb.toString(); } }); Map
  • 51. LiveData Transformations em@realm.io LiveData<List<LoanWithUserAndBook>> loanLiveData = mDb.loanModel().findLoansByNameAfter("Mike", getYesterdayDate()); // Instead of exposing the list of Loans, we can apply a transformation and expose Strings. LiveData<String> stringLiveData = Transformations.map(loanLiveData, new Function<List<LoanWithUserAndBook>, String>() { @Override public String apply(List<LoanWithUserAndBook> loansWithUserAndBook) { StringBuilder sb = new StringBuilder(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US); for (LoanWithUserAndBook loan : loansWithUserAndBook) { sb.append(String.format("%sn (Returned: %s)n", loan.bookTitle, simpleDateFormat.format(loan.endTime))); } return sb.toString(); } }); Map
  • 52. LiveData Transformations em@realm.io LiveData<List<LoanWithUserAndBook>> loanLiveData = mDb.loanModel().findLoansByNameAfter("Mike", getYesterdayDate()); // Instead of exposing the list of Loans, we can apply a transformation and expose Strings. LiveData<String> stringLiveData = Transformations.map(loanLiveData, new Function<List<LoanWithUserAndBook>, String>() { @Override public String apply(List<LoanWithUserAndBook> loansWithUserAndBook) { StringBuilder sb = new StringBuilder(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US); for (LoanWithUserAndBook loan : loansWithUserAndBook) { sb.append(String.format("%sn (Returned: %s)n", loan.bookTitle, simpleDateFormat.format(loan.endTime))); } return sb.toString(); } }); Map
  • 53. LiveData em@realm.io Subscribing to LiveData // Here an Activity, Subscribed to LiveData
 public class CustomResultUserActivity extends AppCompatActivity { private CustomResultViewModel mShowUserViewModel; private TextView mBooksTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.db_activity); mBooksTextView = (TextView) findViewById(R.id.books_tv); mShowUserViewModel = ViewModelProviders.of(this).get(CustomResultViewModel.class); mShowUserViewModel.getLoansResult().observe(this, new Observer<String>() { @Override public void onChanged(@Nullable final String result) { mBooksTextView.setText(result); } }); } }
  • 54. LiveData em@realm.io Subscribing to LiveData // Here an Activity, Subscribed to LiveData
 public class CustomResultUserActivity extends AppCompatActivity { private CustomResultViewModel mShowUserViewModel; private TextView mBooksTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.db_activity); mBooksTextView = (TextView) findViewById(R.id.books_tv); mShowUserViewModel = ViewModelProviders.of(this).get(CustomResultViewModel.class); mShowUserViewModel.getLoansResult().observe(this, new Observer<String>() { @Override public void onChanged(@Nullable final String result) { mBooksTextView.setText(result); } }); } } LiveData respects lifecycle of
  • 55. LiveData em@realm.io public class RealmLiveData<T extends RealmModel> extends LiveData<RealmResults<T>> { private RealmResults<T> mResults; private RealmChangeListener<RealmResults<T>> listener = new RealmChangeListener<RealmResults<T>>() { @Override public void onChange(RealmResults<T> results) { setValue(results); } }; public RealmLiveData(RealmResults<T> mResults) { this.mResults = mResults; } @Override protected void onActive() { mResults.addChangeListener(listener); } @Override protected void onInactive() { mResults.removeChangeListener(listener); } } Extensible - Represent anything as LiveData
  • 56. LiveData em@realm.io public class RealmLiveData<T extends RealmModel> extends LiveData<RealmResults<T>> { private RealmResults<T> mResults; private RealmChangeListener<RealmResults<T>> listener = new RealmChangeListener<RealmResults<T>>() { @Override public void onChange(RealmResults<T> results) { setValue(results); } }; public RealmLiveData(RealmResults<T> mResults) { this.mResults = mResults; } @Override protected void onActive() { mResults.addChangeListener(listener); } @Override protected void onInactive() { mResults.removeChangeListener(listener); } }
  • 57. LiveData em@realm.io public class RealmLiveData<T extends RealmModel> extends LiveData<RealmResults<T>> { private RealmResults<T> mResults; private RealmChangeListener<RealmResults<T>> listener = new RealmChangeListener<RealmResults<T>>() { @Override public void onChange(RealmResults<T> results) { setValue(results); } }; public RealmLiveData(RealmResults<T> mResults) { this.mResults = mResults; } @Override protected void onActive() { mResults.addChangeListener(listener); } @Override protected void onInactive() { mResults.removeChangeListener(listener); } }
  • 58. LiveData em@realm.io // Use Custom LiveData
 public RealmLiveData<Loan> findLoansByNameAfter(final String userName, final Date after) { return new RealmLiveData<>(mRealm.where(Loan.class) .like("user.name", userName) .greaterThan("endTime", after) .findAllAsync()); } // Can even transform that live data RealmLiveData<Loan> loans = findLoansByNameAfter(“Mike”, getYesterdayDate()); LiveData<String> mLoansResult = Transformations.map(loans, new Function<RealmResults<Loan>, String>() { @Override public String apply(RealmResults<Loan> loans) { StringBuilder sb = new StringBuilder(); for (Loan loan : loans) { sb.append(String.format("%sn (Returned: %s)n", loan.getBook().getTitle(), simpleDateFormat.format(loan.getEndTime()))); } return sb.toString(); } });
  • 59. LiveData em@realm.io // Use Custom LiveData
 public RealmLiveData<Loan> findLoansByNameAfter(final String userName, final Date after) { return new RealmLiveData<>(mRealm.where(Loan.class) .like("user.name", userName) .greaterThan("endTime", after) .findAllAsync()); } // Can even transform that live data RealmLiveData<Loan> loans = findLoansByNameAfter(“Mike”, getYesterdayDate()); LiveData<String> mLoansResult = Transformations.map(loans, new Function<RealmResults<Loan>, String>() { @Override public String apply(RealmResults<Loan> loans) { StringBuilder sb = new StringBuilder(); for (Loan loan : loans) { sb.append(String.format("%sn (Returned: %s)n", loan.getBook().getTitle(), simpleDateFormat.format(loan.getEndTime()))); } return sb.toString(); } });
  • 60. LiveData em@realm.io // Use Custom LiveData
 public RealmLiveData<Loan> findLoansByNameAfter(final String userName, final Date after) { return new RealmLiveData<>(mRealm.where(Loan.class) .like("user.name", userName) .greaterThan("endTime", after) .findAllAsync()); } // Can even transform that live data RealmLiveData<Loan> loans = findLoansByNameAfter(“Mike”, getYesterdayDate()); LiveData<String> mLoansResult = Transformations.map(loans, new Function<RealmResults<Loan>, String>() { @Override public String apply(RealmResults<Loan> loans) { StringBuilder sb = new StringBuilder(); for (Loan loan : loans) { sb.append(String.format("%sn (Returned: %s)n", loan.getBook().getTitle(), simpleDateFormat.format(loan.getEndTime()))); } return sb.toString(); } });
  • 62. em@realm.io Room • ORM for SQLite • DAOs defined in interface are generated at compile time • Results are Live(Data)
  • 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