1 of 76

Software

Architecture

Component

2 of 76

Muh Isfhani Ghiath

Former Lead, Google DSC

Sr. Software Engineer Android

Tech Lead, Sanggar ID

@isfaaghyth

Instructor, AEJ Jakarta

3 of 76

Activity

Broadcast Receiver

Content Provider

Fragment

Is this Android architecture pattern?

It’s only contract

4 of 76

MVP

Activity

Broadcast Receiver

Content Provider

Fragment

MVI

MVVM

MVC

5 of 76

Google official guide for app architecture

6 of 76

Beginner Architectural Design Pattern

Main Activity

7 of 76

Beginner Architectural Design Pattern

@Nullable

@Override

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

View v = inflater.inflate(R.layout.fragment_kategori_tab, container, false);

ButterKnife.bind(this,v);

setLoading(true);

SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());

String token = sharedPreferences.getString(getString(R.string.key_token), null);

if (token != null){

Retrofit retrofit = APIClient.getClient();

APIService apiService = retrofit.create(APIService.class);

Call<SearchResponse> result = apiService.getProductByKategori("api/v1/product/category/"

+ String.valueOf(getArguments().getInt("data")));

result.enqueue(new Callback<SearchResponse>() {

@Override

public void onResponse(Call<SearchResponse> call, Response<SearchResponse> response) {

...

8 of 76

Beginner Architectural Design Pattern

Pros:

  • Easy to learn
  • Fast Development

Cons:

  • Hard maintenance
  • No separation of concern
  • Hard to test
  • Not readable

9 of 76

Common Architectur : MVP

Model

View (Activity, fragment...)

Presenter

Update UI

User Interaction

Model Changed

Update Model

10 of 76

Common Architecture : MVP

interface MyView {

fun displayMyData(data: Data)

fun hideMyData()

}

class Presenter {

lateinit var view: MyView?

fun setMyView(myView: MyView) {

this.view = myView

}

fun getData() {

val data = dataRepository.getDataFromNetwork()

view?.displayMyData(data)

}

}

View Reference

11 of 76

  • Might produce NullPointerException

  • View reference can lead to memory leak

12 of 76

OnCreate

presenter.fetchNetwork(myView)

presenter.setView(this)

backgroundTask( onSuccess-> myView.updateData() )

OnDestroy

13 of 76

MVP Architectural Design Pattern

Pros:

  • Separation of concern
  • Maintainable
  • Testable

Cons:

  • Slower development
  • A lot of interface to define interaction between component
  • Presenter has Android UI reference

14 of 76

MVVM Architectural Design Pattern

Model

View (Activity, fragment,...)

ViewModel

Observe data

User Interaction

Model Changed

Update Model

15 of 76

Model ?

16 of 76

Define by yourself

17 of 76

Separate as model

GET RECENT SEARCH

GET RECENT VIEW

GET POPULAR SEARCH

18 of 76

ViewModel ?

19 of 76

Separated from Android framework component

Activity

Viewmodel

Context

Resources

Fragment

20 of 76

Has observable data to communicate with view

Activity

Viewmodel

Observe

No UI Reference

21 of 76

View ?

22 of 76

All about Android framework

Activity

Context

Resources

Fragment

23 of 76

View is dumb

override fun onCreate(savedInstanceState: Bundle?) {

...

mathViewModel.onActivityCreated()

btn_answer.setOnClickListener {

mathViewModel.onButtonAnswerClicked(

Integer.valueOf(edt_equals.text.toString())

)

}

}

override fun onSaveInstanceState(outState: Bundle) {

super.onSaveInstanceState(outState)

mathViewModel.saveViewInstance(uiConfigurationObject)

}

All logic is handled by viewmodel

24 of 76

Observing its viewmodel data

override fun onCreate(savedInstanceState: Bundle?) {

...

yourViewModel.observableData.observe(

this, Observer { data ->

updateView(data)

})

}

25 of 76

MVVM Architectural Design Pattern

Pros:

  • Separation of concern
  • Maintainable
  • Testable
  • No UI reference in viewmodel

Cons:

  • Slower development
  • Can be overkill for a simple feature

26 of 76

Android Architecture Component

27 of 76

Lifecycle-Aware Observables

ViewModel

implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"

implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"

28 of 76

ViewModel

29 of 76

OnCreate

viewModel.getNetworkData()

viewModel.getData()

backgroundTask( onSuccess-> updateViewModelData() )

OnDestroy

DATA A

OnCreate

DATA A

OnDestroy

30 of 76

Has its own lifecycle

Will be retained as long as the given scope is alive (e.g Activity scope)

31 of 76

Has its own Coroutine Scope

class MyViewModel: ViewModel() {

init {

viewModelScope.launch {

// Coroutine that will be canceled when the ViewModel is cleared.

}

}

}

Provided start from :

androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0

32 of 76

How to create a ViewModel?

OR

class YourViewModel : ViewModel() {

...

}

class YourViewModel(app: Application) : AndroidViewModel(app) {

...

}

33 of 76

ViewModel vs AndroidViewModel

It is a safeway API provided by Google to get an Application inside ViewModel easily

However, its better to use ViewModel for better separation between Android framework and Business logic

34 of 76

SavedStateHandle

class MathViewModel(val savedStateHandle: SavedStateHandle) : ViewModel() {

...

}

Replace savedInstanceState implementation in activity/fragment

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

val flag: Boolean = savedInstanceState?.getBoolean("myFlag")?: false

No more:

35 of 76

Why SavedStateHandle?

ViewModel survives

ViewModel can be destroyed

How to survive?

Save to savedStateHandle

Retrieve it later

36 of 76

It has its own providers for View

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

...

yourViewModel = ViewModelProviders.of(this,

YourViewModelFactory(this, Bundle()))

.get(YourViewModel::class.java)

}

}

ViewModelProviders will create new instance if it is called first time. Or get the created instance if it is already created in given scope.

37 of 76

ViewModelProviders parameter

yourViewModel = ViewModelProviders.of(this,

YourViewModelFactory(this, Bundle()))

.get(MathViewModel::class.java)

Viewmodel scope. It can be Activity or Fragment

Viewmodel factory, if your viewmodel has dependency

38 of 76

ViewModel dependency

class YourViewModel(val savedStateHandle: SavedStateHandle,

val modelA: ModelA,

val modelB: ModelB) : ViewModel() {

...

}

39 of 76

Of course my viewmodel has dependency!

Alright, let’s create my custom viewmodel factory

@Suppress("UNCHECKED_CAST")

class YourFactory(val getUserData: GetUserData) : ViewModelProvider.Factory {

override fun <T : ViewModel?> create(modelClass: Class<T>): T {

return YourViewModel(getUserData) as T

}

}

Casting is needed, because abstract factory provided by Android is providing general type of ViewModel

40 of 76

Builder for my SavedStateHandle API?

@Suppress("UNCHECKED_CAST")

class YourViewModelFactory(

savedStateRegistryOwner: SavedStateRegistryOwner,

defaultArgs: Bundle

) : AbstractSavedStateViewModelFactory(savedStateRegistryOwner,defaultArgs) {

override fun <T : ViewModel?> create(

key: String,

modelClass: Class<T>,

handle: SavedStateHandle

): T {

return YourViewModel(handle) as T

}

}

41 of 76

Lifecycle-Aware Observables

42 of 76

RxJava OBSERVABLE

Kotlin

FLOW

Android

LIVEDATA

Observable data type alternative...

43 of 76

LiveData is better

44 of 76

LiveData is observable!

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

...

yourViewModel.observableLiveData.observe(

this, Observer { data ->

refreshView(data)

})

}

}

To observe livedata, we need a lifecycle owner and an observer

45 of 76

What is lifecycle owner?

46 of 76

mathViewModel.problemObservable.observe(

this, Observer { problem ->

refreshView(problem)

})

By providing lifecycle owner when observing a live data, live data will maintain your subscription according to its lifecycle owner.

Which makes it Lifecycle-Aware

47 of 76

mathViewModel.problemObservable.observe(

this, Observer { problem ->

refreshView(problem)

})

Class with default lifecycle owner:

AppCompatActivity

Fragment

DialogFragment

… and many more

You can simply pass this as lifecycle owner

48 of 76

Special case : FRAGMENT

model.yourLiveData.observe(

viewLifecycleOwner, Observer { data ->

updateView(data)

})

Google provides specific viewLifecycleOwner object for Fragment. Because Fragment can have complex lifecycle. Just use that lifecycle owner if you’re using Fragment

49 of 76

How is livedata lifecycle-aware:

View

Viewmodel

50 of 76

onStart, onResume, onPause state

View

Viewmodel

A

A

B

B

51 of 76

onStop, onDestroy, onCreate state

View

Viewmodel

A

A

B

B

C

Livedata not emitting value when lifecycleowner is not in good state to receive data

52 of 76

onStop, onDestroy, onCreate state

View

Viewmodel

A

A

B

B

C

Then it will emit the last data when its lifecycle owner is ready to receive data, which makes livedata a

Data Holder

C

53 of 76

How to create a LiveData?

val yourLiveData : LiveData<Model>

get() { return _yourMutableLiveData }

private val _yourMutableLiveData = MutableLiveData<Model>()

Expose your immutable LiveData, we don’t expect our view change our data, right?

54 of 76

Set LiveData Value

OR

viewModelScope.launch {

model = domain.createModel()

_yourMutableLiveData.value = model

}

viewModelScope.launch {

model = domain.createModel()

_yourMutableLiveData.postValue(model)

}

55 of 76

Use Live data setValue() when

Use Live data postValue() when

LiveData

Observer

MainThread

MainThread

LiveData

Observer

BackgroundThread

MainThread

56 of 76

LiveData transformations

57 of 76

Map - One on one static transformation

val localData: LiveData = ...;

val viewData = localData.map { it ->

...

}

Local Repository

ViewModel

View

LiveData

<LocalData>

LiveData

<ViewData>

Observe..

Observe..

fun <X, Y> LiveData<X>.map(crossinline transform: (X) -> Y): LiveData<Y>

58 of 76

Local Repository

ViewModel

View

LiveData

<LocalData>

LiveData

<ViewData>

LifecycleOwner

LifecycleOwner

Subscription still maintainable on map function

Observe..

Observe..

59 of 76

SwitchMap - One on one dynamic transformation

User Id Repository

ViewModel

View

getUserId():

LiveData<UserId>

getData():

LiveData

<ViewData>

Observe..

Observe..

User Address Repository

getAddressOfUser(userId): LiveData<Address>

Observe..

60 of 76

Similar to map, applies a function to the value stored in the LiveData object and unwraps and dispatches the result downstream. The function passed to switchMap() must return a LiveData object.

class UserIdRepository {

fun getUserId(): LiveData<Int>

}

class UserAddressRepository {

fun getUserAddress(id: Int): LiveData<Address>

}

val viewData = userIdRepository.getUserId().switchMap { id->

userAddressRepository.get(id)

}

inline fun <X, Y> LiveData<X>.switchMap(

crossinline transform: (X) -> LiveData<Y>

): LiveData<Y>

61 of 76

One to many transformation

Mediator LiveData

LiveData subclass which may observe other LiveData object and react onChanged event from them

62 of 76

val isRecommendedLiveData = MediatorLiveData<Boolean>();

val priceLiveData: LiveData<Int> = ...;

isRecommendedLiveData.addSource(priceLiveData) price -> {

if(price<10000) isRecommendedLiveData.value = true

else isRecommendedLiveData.value = false

}

63 of 76

Let’s create MVVM project!

Psst.. it’s super easy

64 of 76

Let’s build some project !

Create simple kids game app:

  1. Display math problem with imageview
  2. Display stage and scores
  3. Provide button to submit answer
  4. Re-generate problem every answer button clicked

65 of 76

Step 1 : Define your model

class MathDomain {

fun createProblem(): Problem {

val num1 = Random.nextInt(0,10)

val num2 = Random.nextInt(0,10)

val answer = num1 + num2

return Problem(

num1 = num1,

num2 = num2,

answer = answer

)

}

}

66 of 76

Step 1-extended : Define your (testable) model

Your model can be suspendable

class MathDomain {

suspend fun suspendCreateProblem(): Problem {

delay(5000)

val num1 = Random.nextInt(0,10)

val num2 = Random.nextInt(0,10)

val answer = num1 + num2

return Problem(

num1 = num1,

num2 = num2,

answer = answer

)

}

}

67 of 76

Step 2 : Create your ViewModel

class MathViewModel(val savedStateHandle: SavedStateHandle,

val mathDomain: MathDomain) : ViewModel() {

var scores = 0

var count = 0

fun refreshProblem() {

...

}

fun answerProblem(answer: Int) {

...

}

}

68 of 76

Step 3 : Expose your (observable) immutable data

class MathViewModel(val savedStateHandle: SavedStateHandle,

val mathDomain: MathDomain) : ViewModel() {

val problemObservable : LiveData<Problem>

get() { return _problemObservable }

private val _problemObservable = MutableLiveData<Problem>()

}

69 of 76

Step 4 : Create viewmodel factory

@Suppress("UNCHECKED_CAST")

class MathViewModelFactory(

savedStateRegistryOwner: SavedStateRegistryOwner,

defaultArgs: Bundle

) : AbstractSavedStateViewModelFactory(savedStateRegistryOwner, defaultArgs) {

override fun <T : ViewModel?> create(

key: String,

modelClass: Class<T>,

handle: SavedStateHandle

): T {

return MathViewModel(handle, MathDomain()) as T

}

}

70 of 76

Step 5 : Get your viewmodel from UI

class MainActivity : AppCompatActivity() {

private val mathViewModel: MathViewModel by viewModels {

MathViewModelFactory(this, Bundle())

}

OR

override fun onCreate(savedInstanceState: Bundle?) {

...

mathViewModel = ViewModelProviders.of(this,

MathViewModelFactory(this, Bundle()))

.get(MathViewModel::class.java)

}

}

71 of 76

Step 6 : Observe your viewmodel data

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

...

mathViewModel.problemObservable.observe(

this, Observer { problem ->

refreshView(problem)

})

}

}

72 of 76

Step 7 : Make your UI dumb!

override fun onCreate(savedInstanceState: Bundle?) {

...

mathViewModel.onActivityCreated()

btn_answer.setOnClickListener {

mathViewModel.onButtonAnswerClicked(

Integer.valueOf(edt_equals.text.toString())

)

}

}

override fun onSaveInstanceState(outState: Bundle) {

super.onSaveInstanceState(outState)

mathViewModel.saveViewInstance(uiConfigurationObject)

}

73 of 76

Conclusion

  1. Don’t let ViewModels (and Presenters) know about Android framework classes (Separate UI Logic from business logic)
  2. Make your UI as dumb as possible
  3. Avoid view reference in viewmodel
  4. Let UI observe viewmodel data instead of change UI data based on reference
  5. Don’t expose mutable observable to view
  6. Use SavedStateHandle to retains view state in viewmodel

74 of 76

Extras : Coroutine

Another API for running task in the background

Known for its suspending function

All function which needs to run in the background needs to have suspend modifier. So it can be suspendable

75 of 76

How to run suspend function

viewModelScope.launch {

withContext(Dispatchers.IO) {

model = domain.suspendGetModel()

_yourLiveData.postValue(model)

}

}

In most cases, coroutine calls is done in viewmodel, and ViewModel already provided specific coroutineScope

We need coroutineScope and coroutineContext

76 of 76

That’s it!