Software
Architecture
Component
Muh Isfhani Ghiath
Former Lead, Google DSC
Sr. Software Engineer Android
Tech Lead, Sanggar ID
@isfaaghyth
Instructor, AEJ Jakarta
Activity
Broadcast Receiver
Content Provider
Fragment
Is this Android architecture pattern?
It’s only contract
MVP
Activity
Broadcast Receiver
Content Provider
Fragment
MVI
MVVM
MVC
Google official guide for app architecture
Beginner Architectural Design Pattern
Main Activity
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) {
...
Beginner Architectural Design Pattern
Pros:
Cons:
Common Architectur : MVP
Model
View (Activity, fragment...)
Presenter
Update UI
User Interaction
Model Changed
Update Model
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
OnCreate
presenter.fetchNetwork(myView)
presenter.setView(this)
backgroundTask( onSuccess-> myView.updateData() )
OnDestroy
MVP Architectural Design Pattern
Pros:
Cons:
MVVM Architectural Design Pattern
Model
View (Activity, fragment,...)
ViewModel
Observe data
User Interaction
Model Changed
Update Model
Model ?
Define by yourself
Separate as model
GET RECENT SEARCH
GET RECENT VIEW
GET POPULAR SEARCH
ViewModel ?
Separated from Android framework component
Activity
Viewmodel
Context
Resources
Fragment
Has observable data to communicate with view
Activity
Viewmodel
Observe
No UI Reference
View ?
All about Android framework
Activity
Context
Resources
Fragment
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
Observing its viewmodel data
override fun onCreate(savedInstanceState: Bundle?) {
...
yourViewModel.observableData.observe(
this, Observer { data ->
updateView(data)
})
}
MVVM Architectural Design Pattern
Pros:
Cons:
Android Architecture Component
Lifecycle-Aware Observables
ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
ViewModel
OnCreate
viewModel.getNetworkData()
viewModel.getData()
backgroundTask( onSuccess-> updateViewModelData() )
OnDestroy
DATA A
OnCreate
DATA A
OnDestroy
Has its own lifecycle
Will be retained as long as the given scope is alive (e.g Activity scope)
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
How to create a ViewModel?
OR
class YourViewModel : ViewModel() {
...
}
class YourViewModel(app: Application) : AndroidViewModel(app) {
...
}
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
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:
Why SavedStateHandle?
ViewModel survives
ViewModel can be destroyed
How to survive?
Save to savedStateHandle
Retrieve it later
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.
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
ViewModel dependency
class YourViewModel(val savedStateHandle: SavedStateHandle,
val modelA: ModelA,
val modelB: ModelB) : ViewModel() {
...
}
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
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
}
}
Lifecycle-Aware Observables
RxJava OBSERVABLE
Kotlin
FLOW
Android
LIVEDATA
Observable data type alternative...
LiveData is better
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
What is lifecycle owner?
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
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
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
How is livedata lifecycle-aware:
View
Viewmodel
onStart, onResume, onPause state
View
Viewmodel
A
A
B
B
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
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
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?
Set LiveData Value
OR
viewModelScope.launch {
model = domain.createModel()
_yourMutableLiveData.value = model
}
viewModelScope.launch {
model = domain.createModel()
_yourMutableLiveData.postValue(model)
}
Use Live data setValue() when
Use Live data postValue() when
LiveData
Observer
MainThread
MainThread
LiveData
Observer
BackgroundThread
MainThread
LiveData transformations
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>
Local Repository
ViewModel
View
LiveData
<LocalData>
LiveData
<ViewData>
LifecycleOwner
LifecycleOwner
Subscription still maintainable on map function
Observe..
Observe..
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..
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>
One to many transformation
Mediator LiveData
LiveData subclass which may observe other LiveData object and react onChanged event from them
val isRecommendedLiveData = MediatorLiveData<Boolean>();
val priceLiveData: LiveData<Int> = ...;
isRecommendedLiveData.addSource(priceLiveData) price -> {
if(price<10000) isRecommendedLiveData.value = true
else isRecommendedLiveData.value = false
}
Let’s create MVVM project!
Psst.. it’s super easy
Let’s build some project !
Create simple kids game app:
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
)
}
}
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
)
}
}
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) {
...
}
}
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>()
}
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
}
}
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)
}
}
Step 6 : Observe your viewmodel data
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
mathViewModel.problemObservable.observe(
this, Observer { problem ->
refreshView(problem)
})
}
}
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)
}
Conclusion
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
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
That’s it!