Slide 1

Slide 1 text

@riggaroo Android Architecture Components A Testable Approach to Android Development Rebecca Franks

Slide 2

Slide 2 text

HELLO! I am Rebecca Franks Android Developer Google Developer Expert @riggaroo

Slide 3

Slide 3 text

What are the Android Architecture Components? Powerful APIs that are testable " Improve app quality ✅ Address common issues Best practice guidelines %

Slide 4

Slide 4 text

@riggaroo Components - Room - ViewModels - LiveData * - Lifecycle * - Paging Library * - …

Slide 5

Slide 5 text

Architecting an App

Slide 6

Slide 6 text

bit.ly/android-arch bit.ly/android-arch-kotlin

Slide 7

Slide 7 text

MVVM Model, View, ViewModel

Slide 8

Slide 8 text

MVVM View ViewModel Model

Slide 9

Slide 9 text

View ViewModel Model Retrofit API Room Database Web Service SQLite MVVM

Slide 10

Slide 10 text

View ViewModel Model Retrofit API Room Database Web Service SQLite MVVM

Slide 11

Slide 11 text

Room - SQLite Object Mapper

Slide 12

Slide 12 text

Creating an Entity @Entity(tableName = TABLE_NAME) public class Event { @PrimaryKey(autoGenerate = true) private int id; private String name; private LocalDateTime date; public int getId() { return id; } public String getName() { return name; } public LocalDateTime getDate() { return date; } }

Slide 13

Slide 13 text

Creating a DAO @Dao public interface EventDao { @Insert(onConflict = OnConflictStrategy.REPLACE) void addEvent(Event event); @Query("SELECT * FROM " + Event.TABLE_NAME + " WHERE " + Event.DATE_FIELD + " > :minDate") LiveData> getEvents(LocalDateTime minDate); }

Slide 14

Slide 14 text

Creating a Database @Database(entities = {Event.class}, version = 1) public abstract class EventDatabase extends RoomDatabase { public abstract EventDao eventDao(); }

Slide 15

Slide 15 text

Create an instance of the DB @Provides @Singleton EventDatabase providesEventDatabase(CountdownApplication context) { return Room.databaseBuilder(context, EventDatabase.class, “event.db").build(); }

Slide 16

Slide 16 text

Model

Slide 17

Slide 17 text

View ViewModel Model Retrofit API Room Database Web Service SQLite MVVM

Slide 18

Slide 18 text

Model Model layer follows Repository pattern ie EventRepository, LoginRepository MVVM - Model

Slide 19

Slide 19 text

Repository

Slide 20

Slide 20 text

Repository Business Logic, Caching Plain object No Android Dependencies

Slide 21

Slide 21 text

public class EventRepositoryImpl implements EventRepository { @Inject EventDao eventDao; public EventRepositoryImpl(EventDao eventDao) { this.eventDao = eventDao; } @Override public Completable addEvent(Event event) { if (event == null){ return Completable.error(new IllegalArgumentException("Event cannot be null")); } return Completable.fromAction(() -> eventDao.addEvent(event)); } }

Slide 22

Slide 22 text

public class EventRepositoryImpl implements EventRepository { @Inject EventDao eventDao; public EventRepositoryImpl(EventDao eventDao) { this.eventDao = eventDao; } @Override public Completable addEvent(Event event) { if (event == null){ return Completable.error(new IllegalArgumentException("Event cannot be null")); } return Completable.fromAction(() -> eventDao.addEvent(event)); } } Completable is used when the Observable has to do some task without emitting a value.

Slide 23

Slide 23 text

ViewModel

Slide 24

Slide 24 text

View ViewModel Model Retrofit API Room Database Web Service SQLite MVVM

Slide 25

Slide 25 text

ViewModel AddEventViewModel, EventListViewModel MVVM - ViewModel

Slide 26

Slide 26 text

What is a ViewModel? - The VM in MVVM architecture - Store & manage UI-related data - Data survives config changes - No leaks

Slide 27

Slide 27 text

public class AddEventViewModel extends ViewModel { @Inject EventRepository eventRepository; private String eventName; private String eventDescription; private LocalDateTime eventDateTime ; @Inject AddEventViewModel() { eventDateTime = LocalDateTime.now(); } void addEvent() { Event event = new Event(0, eventName, eventDescription, eventDateTime); eventRepository.addEvent(event).observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(new CompletableObserver() { @Override public void onSubscribe(Disposable d) { } Creating your ViewModel

Slide 28

Slide 28 text

@Inject AddEventViewModel() { eventDateTime = LocalDateTime.now(); } void addEvent() { Event event = new Event(0, eventName, eventDescription, eventDateTime); eventRepository.addEvent(event).observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(new CompletableObserver() { @Override public void onSubscribe(Disposable d) { } @Override public void onComplete() { Log.d(TAG, ”onComplete - successfully added event"); } @Override public void onError(Throwable e) { Log.d(TAG, e, "onError - add:"); } }); }

Slide 29

Slide 29 text

@Inject AddEventViewModel() { eventDateTime = LocalDateTime.now(); } void addEvent() { Event event = new Event(0, eventName, eventDescription, eventDateTime); eventRepository.addEvent(event).observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(new CompletableObserver() { @Override public void onSubscribe(Disposable d) { } @Override public void onComplete() { Log.d(TAG, ”onComplete - successfully added event"); } @Override public void onError(Throwable e) { Log.d(TAG, e, "onError - add:"); } }); }

Slide 30

Slide 30 text

View

Slide 31

Slide 31 text

View ViewModel Model Retrofit API Room Database Web Service SQLite MVVM

Slide 32

Slide 32 text

View AddEventFragment, EventListFragment MVVM - View

Slide 33

Slide 33 text

public class AddEventFragment extends Fragment { private Button buttonAddEvent; private AddEventViewModel addEventViewModel; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_add_event, container, false); //.. setupClickListeners(); setupViewModel(); return view; } private void setupViewModel() { addEventViewModel = ViewModelProviders.of(this).get(AddEventViewModel.class); } private void setupClickListeners() { buttonAddEvent.setOnClickListener(v -> { addEventViewModel.addEvent(); getActivity().finish(); }); } }

Slide 34

Slide 34 text

public class AddEventFragment extends Fragment { private Button buttonAddEvent; private AddEventViewModel addEventViewModel; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_add_event, container, false); //.. setupClickListeners(); setupViewModel(); return view; } private void setupViewModel() { addEventViewModel = ViewModelProviders.of(this).get(AddEventViewModel.class); } private void setupClickListeners() { buttonAddEvent.setOnClickListener(v -> { addEventViewModel.addEvent(); getActivity().finish(); }); } }

Slide 35

Slide 35 text

public class AddEventFragment extends Fragment { private Button buttonAddEvent; private AddEventViewModel addEventViewModel; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_add_event, container, false); //.. setupClickListeners(); setupViewModel(); return view; } private void setupViewModel() { addEventViewModel = ViewModelProviders.of(this).get(AddEventViewModel.class); } private void setupClickListeners() { buttonAddEvent.setOnClickListener(v -> { addEventViewModel.addEvent(); getActivity().finish(); }); } }

Slide 36

Slide 36 text

public class AddEventFragment extends Fragment { private Button buttonAddEvent; private AddEventViewModel addEventViewModel; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_add_event, container, false); //.. setupClickListeners(); setupViewModel(); return view; } private void setupViewModel() { addEventViewModel = ViewModelProviders.of(this).get(AddEventViewModel.class); } private void setupClickListeners() { buttonAddEvent.setOnClickListener(v -> { addEventViewModel.addEvent(); getActivity().finish(); }); } }

Slide 37

Slide 37 text

Demo

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

Let’s chat about tests…

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

View ViewModel Model Retrofit API Room Database Web Service SQLite JUnit tests MVVM

Slide 42

Slide 42 text

View ViewModel Model Retrofit API Room Database Web Service SQLite Instrumentation Tests MVVM

Slide 43

Slide 43 text

Abstract out Android Logic as much as possible. Write plenty of JUnit Tests, not as many UI Tests

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

@riggaroo JUnit Tests - Run on JVM - No Android Dependencies - Use Mockito - mock out everything but current class under test

Slide 46

Slide 46 text

@riggaroo Instrumentation Tests - Two Types: - Android Unit Test - Use Android dependencies - No UI - UI Test - Use Espresso - Physical clicks on device - Hardly any mocking (maybe HTTP responses)

Slide 47

Slide 47 text

Testing Database Queries

Slide 48

Slide 48 text

View ViewModel Model Retrofit API Room Database Web Service SQLite Testing Room

Slide 49

Slide 49 text

@RunWith(AndroidJUnit4.class) public class EventDaoTest { EventDao eventDao; EventDatabase eventDatabase; @Before public void setup() { eventDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), EventDatabase.class).build(); eventDao = eventDatabase.eventDao(); } @Test public void addEvent_SuccessfullyAddsEvent() throws InterruptedException { Event event = generateEventTestData(0, "Wedding"); eventDao.addEvent(event); List eventRetrieved = getValue(eventDao.getEvents(LocalDateTime.now())); assertEquals(event.getName(), eventRetrieved.get(0).getName()); } }

Slide 50

Slide 50 text

@RunWith(AndroidJUnit4.class) public class EventDaoTest { EventDao eventDao; EventDatabase eventDatabase; @Before public void setup() { eventDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), EventDatabase.class).build(); eventDao = eventDatabase.eventDao(); } @Test public void addEvent_SuccessfullyAddsEvent() throws InterruptedException { Event event = generateEventTestData(0, "Wedding"); eventDao.addEvent(event); List eventRetrieved = getValue(eventDao.getEvents(LocalDateTime.now())); assertEquals(event.getName(), eventRetrieved.get(0).getName()); } }

Slide 51

Slide 51 text

@RunWith(AndroidJUnit4.class) public class EventDaoTest { EventDao eventDao; EventDatabase eventDatabase; @Before public void setup() { eventDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), EventDatabase.class).build(); eventDao = eventDatabase.eventDao(); } @Test public void addEvent_SuccessfullyAddsEvent() throws InterruptedException { Event event = generateEventTestData(0, "Wedding"); eventDao.addEvent(event); List eventRetrieved = getValue(eventDao.getEvents(LocalDateTime.now())); assertEquals(event.getName(), eventRetrieved.get(0).getName()); } }

Slide 52

Slide 52 text

@RunWith(AndroidJUnit4.class) public class EventDaoTest { EventDao eventDao; EventDatabase eventDatabase; @Before public void setup() { eventDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), EventDatabase.class).build(); eventDao = eventDatabase.eventDao(); } @Test public void addEvent_SuccessfullyAddsEvent() throws InterruptedException { Event event = generateEventTestData(0, "Wedding"); eventDao.addEvent(event); List eventRetrieved = getValue(eventDao.getEvents(LocalDateTime.now())); assertEquals(event.getName(), eventRetrieved.get(0).getName()); } }

Slide 53

Slide 53 text

Testing Models/ Repositories

Slide 54

Slide 54 text

View ViewModel Model Retrofit API Room Database Web Service SQLite Model Tests

Slide 55

Slide 55 text

public class EventRepositoryImplTest { @Mock private EventDao eventDao; @Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule(); private EventRepository eventRepository; @Before public void setUp() { MockitoAnnotations.initMocks(this); eventRepository = new EventRepositoryImpl(eventDao); } @Test public void addEvent_TriggersDbAdd(){ Event event = FakeEventDataGenerator.getFakeEvent(); eventRepository.addEvent(event).test().onComplete(); verify(eventDao).addEvent(event); } }

Slide 56

Slide 56 text

public class EventRepositoryImplTest { @Mock private EventDao eventDao; @Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule(); private EventRepository eventRepository; @Before public void setUp() { MockitoAnnotations.initMocks(this); eventRepository = new EventRepositoryImpl(eventDao); } @Test public void addEvent_TriggersDbAdd(){ Event event = FakeEventDataGenerator.getFakeEvent(); eventRepository.addEvent(event).test().onComplete(); verify(eventDao).addEvent(event); } }

Slide 57

Slide 57 text

public class EventRepositoryImplTest { @Mock private EventDao eventDao; @Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule(); private EventRepository eventRepository; @Before public void setUp() { MockitoAnnotations.initMocks(this); eventRepository = new EventRepositoryImpl(eventDao); } @Test public void addEvent_TriggersDbAdd(){ Event event = FakeEventDataGenerator.getFakeEvent(); eventRepository.addEvent(event).test().onComplete(); verify(eventDao).addEvent(event); } }

Slide 58

Slide 58 text

Testing ViewModels

Slide 59

Slide 59 text

View ViewModel Model Retrofit API Room Database Web Service SQLite View Model Tests

Slide 60

Slide 60 text

public class AddEventViewModelTest { AddEventViewModel addEventViewModel; @Mock EventRepository eventRepository; @Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule(); @Before public void setup() { MockitoAnnotations.initMocks(this); addEventViewModel = new AddEventViewModel(); addEventViewModel.eventRepository = eventRepository; } @Test public void addEvent() { when(eventRepository.addEvent(any())).thenReturn(Completable.complete()); addEventViewModel.addEvent(); verify(eventRepository).addEvent(any()); }

Slide 61

Slide 61 text

public class AddEventViewModelTest { AddEventViewModel addEventViewModel; @Mock EventRepository eventRepository; @Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule(); @Before public void setup() { MockitoAnnotations.initMocks(this); addEventViewModel = new AddEventViewModel(); addEventViewModel.eventRepository = eventRepository; } @Test public void addEvent() { when(eventRepository.addEvent(any())).thenReturn(Completable.complete()); addEventViewModel.addEvent(); verify(eventRepository).addEvent(any()); }

Slide 62

Slide 62 text

public class AddEventViewModelTest { AddEventViewModel addEventViewModel; @Mock EventRepository eventRepository; @Rule public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule(); @Before public void setup() { MockitoAnnotations.initMocks(this); addEventViewModel = new AddEventViewModel(); addEventViewModel.eventRepository = eventRepository; } @Test public void addEvent() { when(eventRepository.addEvent(any())).thenReturn(Completable.complete()); addEventViewModel.addEvent(); verify(eventRepository).addEvent(any()); }

Slide 63

Slide 63 text

View Tests ☕

Slide 64

Slide 64 text

View ViewModel Model Retrofit API Room Database Web Service SQLite View Tests

Slide 65

Slide 65 text

Espresso Basic Formula

Slide 66

Slide 66 text

@RunWith(AndroidJUnit4.class) public class AddEventFragmentTest { @Rule public ActivityTestRule activityTestRule = new ActivityTestRule<>(AddEventActivity.class, true, true); private AddEventViewModel addEventViewModel; @Before public void setup(){ addEventViewModel = mock(AddEventViewModel.class); //.. } @Test public void addEvent(){ when(addEventViewModel.getEventDateTime()).thenReturn(LocalDateTime.now()); onView(withId(R.id.edit_text_title)).perform(typeText("Christmas Day"), closeSoftKeyboard()); onView(withId(R.id.button_set_date)).perform(click()); onView(withText("OK")).perform(scrollTo(), click()); onView(withId(R.id.button_add)).perform(click());

Slide 67

Slide 67 text

public ActivityTestRule activityTestRule = new ActivityTestRule<>(AddEventActivity.class, true, true); private AddEventViewModel addEventViewModel; @Before public void setup(){ addEventViewModel = mock(AddEventViewModel.class); //.. } @Test public void addEvent(){ when(addEventViewModel.getEventDateTime()).thenReturn(LocalDateTime.now()); onView(withId(R.id.edit_text_title)).perform(typeText("Christmas Day"), closeSoftKeyboard()); onView(withId(R.id.button_set_date)).perform(click()); onView(withText("OK")).perform(scrollTo(), click()); onView(withId(R.id.button_add)).perform(click()); } }

Slide 68

Slide 68 text

public ActivityTestRule activityTestRule = new ActivityTestRule<>(AddEventActivity.class, true, true); private AddEventViewModel addEventViewModel; @Before public void setup(){ addEventViewModel = mock(AddEventViewModel.class); //.. } @Test public void addEvent(){ when(addEventViewModel.getEventDateTime()).thenReturn(LocalDateTime.now()); onView(withId(R.id.edit_text_title)).perform(typeText("Christmas Day"), closeSoftKeyboard()); onView(withId(R.id.button_set_date)).perform(click()); onView(withText("OK")).perform(scrollTo(), click()); onView(withId(R.id.button_add)).perform(click()); } }

Slide 69

Slide 69 text

public ActivityTestRule activityTestRule = new ActivityTestRule<>(AddEventActivity.class, true, true); private AddEventViewModel addEventViewModel; @Before public void setup(){ addEventViewModel = mock(AddEventViewModel.class); //.. } @Test public void addEvent(){ when(addEventViewModel.getEventDateTime()).thenReturn(LocalDateTime.now()); onView(withId(R.id.edit_text_title)).perform(typeText("Christmas Day"), closeSoftKeyboard()); onView(withId(R.id.button_set_date)).perform(click()); onView(withText("OK")).perform(scrollTo(), click()); onView(withId(R.id.button_add)).perform(click()); } }

Slide 70

Slide 70 text

Running UI Tests

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

What about Android classes or static methods?

Slide 73

Slide 73 text

JUnit tests View ViewModel Repository ResourceProvider SharedPrefProvider MVVM

Slide 74

Slide 74 text

Android Unit tests View ViewModel Repository ResourceProvider SharedPrefProvider MVVM

Slide 75

Slide 75 text

public class ResourceProvider { private final Context context; public ResourceProvider(Context context){ this.context = context; } public String getString(Integer resId){ return context.getString(resId); } }

Slide 76

Slide 76 text

No content

Slide 77

Slide 77 text

Summary Encourages clean code ( UI Tests using Espresso Less memory leaks and bugs Simplifies code

Slide 78

Slide 78 text

@riggaroo Thank you! developer.android.com/arch Rebecca Franks @riggaroo