0% found this document useful (0 votes)
5 views80 pages

Makassar - Design Architecture Components - Past, Present, Future

The document discusses architectural patterns in software development, focusing on the evolution from traditional architectures to modern approaches like MVVM and MVI. It emphasizes the importance of clean architecture for scalability, maintainability, and testability in Android applications. The presentation outlines various architectural principles and introduces an Update Framework to enhance separation of concerns and lifecycle management in app development.

Uploaded by

Ali Asgar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views80 pages

Makassar - Design Architecture Components - Past, Present, Future

The document discusses architectural patterns in software development, focusing on the evolution from traditional architectures to modern approaches like MVVM and MVI. It emphasizes the importance of clean architecture for scalability, maintainability, and testability in Android applications. The presentation outlines various architectural principles and introduces an Update Framework to enhance separation of concerns and lifecycle management in app development.

Uploaded by

Ali Asgar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 80

Architecture Components

Past, Present, Future

GDG Makassar
Muh Isfhani Ghiath (ippang)
Software Engineer, Ecommerce
Tech Lead, Kepul.id
GDE for Android

@isfaaghyth with.isfa.dev
Overview

Outline
What

Why

How

Then?
Architecture?
Overview

What is Architecture
Pattern?
An architectural pattern is a general, reusable
solution to a commonly occurring problem in
software architecture within a given context.
Architectural patterns are similar to software design
pattern but have a broader scope.

Wikipedia
Clean
Architecture

Uncle Bob
Overview

Well architecture app, easier


to:
Test
Make Robust
Expand
Team Develop
Data
Logic
Repository Use Case
Data Separation of concerns
UI Data Services User Interface
Networking
Layered
Architecture

UI Layer

Data Layer
Layered
Architecture

UI Layer

User interactions Text, Images


OS interactions Button/onClick behavior
App layout and screens Text edit fields/user input
Layered
Architecture

Data
UI Layer
Layer

Information in the app Email, subject, body (email)


Databases/network Article title, contents (news)
Model
Brief 🧐
Overview

MVP MVI MVVM MVC


Before
Architecture

Before Architecturing

MainActivit
y
Before
Architecture
@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) {
...
Before
Architecture

Pros Cons
● Easy to Learn ● Hard to Maintain
● Fast Development ● No Separation of Concern
● Hard to Test
● Not readable
MV
P

Model Changed User Interaction

View (Activity,
Model Presenter
fragment...)

Update UI
Update Model
MV
P

interface MyView {
fun displayMyData(data: Data)
fun hideMyData()
}

class Presenter {
lateinit var view: MyView? View Reference

fun setMyView(myView: MyView) {


this.view = myView
}

fun getData() {
val data = dataRepository.getDataFromNetwork()
view?.displayMyData(data)
}
}
MV
P

Issues
● NullPointerException
● Memory Leaks
MV
P

Pros Cons
● Separation of Concern ● Not Sustainable
● Maintainable ● Slower Development
● Testable ● A lot of Interface as
interaction
● UI references on Presenter
MVVM

Model Changed User Interaction

View (Activity,
Model ViewModel
fragment,...)

Observe data
Update Model
MVVM

ViewModel?
Separated from Android framework component Fragment

Context

Viewmodel

Activity

Resources
MVVM

Has observable data to delegate with View

Observe
Viewmodel Activity

No UI Reference
MVVM

View is Dumb! All logic is handled by ViewModel


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)
}
MVVM

Observing its ViewModel data

override fun onCreate(savedInstanceState: Bundle?) {


...
yourViewModel.observableData.observe(
this, Observer { data ->
updateView(data)
})
}
MVVM

Android Architecture
Component
MVVM

Scan here for the


link

Android Architecture
Component
MVVM

Pros Cons
● Separation of Concern ● ViewModel too bloated
● Maintainable ● Hard to maintain in large
● Testable project
● No UI reference in ViewModel ● Not scale as much
MVI? 🤔
Bidirection
MVVM
al

UI Layer

Events Events Data


Data

Data Layer
Bidirection
MVVM
al

Why Bidirectional not suit us anymore?

App driven by the data

View changes has explicitly changes whenever data is


transformed
Each component holds part of the domain state
class ViewModel {

private var _userInfo = mutableStateFlow<User>()


class SampleActivity {
val userInfo get() = _userInfo
fun getUserInfo() {
fun fetchUserInfo() {
viewModel.fetchUserInfo()
viewModelScope.launch {
}
val result = userInfoRepository.userInfo()
_userInfo.value = result
} }
}
}
Unidirection
MVI
al

UI Layer

Events Data

Data Layer
Unidirection
MVI
al

Why you have to choose an Unidirectional?

Humans express themselves as per the senses they perceive.

The app is treated as another being that senses the inputs


and expresses its output.

The app listens to the user actions and modifies the data it has

The changes in the data are reflected through UI as an


expression of the app
class ViewModel {

private var _state = mutableStateFlow<UiState>()


val state get() = _state

private var event = mutableSharedFlow<Event>(Event.Unknown)

fun setEvent(event: Event) {


viewModelScope.launch {
event.collect {
class SampleActivity { when (it) {
is UserInfo -> fetchUserInfo()
fun getUserInfo() { }
viewModel.setEvent( }
Event.UserInfo }
) }
}
private fun fetchUserInfo() {
} _state.update {
it.copy(
userInfo = userInfoRepository.userInfo()
)
}
}
}
MVVM

Pros Cons
● Driven by Event ● Still, too bloated
● Event treats as User Actions ● Hard to manage multiple actions
● Reactive in nature ● Each different features in a single
● Explicit about side-effects presentation
MVVM+MVI? Why not
both 🤩
Introduce, Update Framework! 🎉
Clean
Architecture

Common architectural principles


(1/2)
As Android apps grow in size, it's important to define an architecture
that allows the app to scale, increases the app's robustness, and
makes the app easier to test.

Android Architecture
Component
Clean
Architecture

Common architectural principles


(2/2)
An app architecture defines the boundaries between parts of the app
and the responsibilities each part should have. In order to meet the
needs mentioned above, we should design the app architecture to follow
a few specific principles.

Android Architecture
Component
Objective
s

Why Update Framework?


Strong separate of concern

Extensible; component represents as Update

Lifecycle-aware

Test encapsulated

Agnostic!
Update Befor
Framework e

UI ViewModel

fun doLogin()
fun fetchTodoList()
fun showTicker()
fun fetchOnboarding()
fun insertTodo(todo: Todo)
fun updateUserProfile()
class FooViewModel(
private val loginUseCase: LoginUseCase,
private val userSessionManager: UserSessionManager,
) : ViewModel()
class FooViewModel(
private val loginUseCase: LoginUseCase,
private val userSessionManager: UserSessionManager,
) : ViewModel() {

private val _login = MutableStateFlow<UserLogin>()


val login get() = _login.asStateFlow()

fun doLogin(email: String, password: String) {


runCatching {
loginUseCase
.request(email, password)
.collect { result ->
userSessionManager.set(result)
_login.update { result }
}
}
}
}
class FooViewModel(
private val loginUseCase: LoginUseCase,
private val userSessionManager: UserSessionManager,
private val fetchTodoUseCase: GetTodoUseCase,
private val insertTodoUseCase: SetTodoUseCase
) : ViewModel() {

private val _login = MutableStateFlow<UserLogin>()


val login get() = _login.asStateFlow()

private val _todoList = MutableStateFlow<TodoList>()


val todoList get() = _todoList.asStateFlow()

fun doLogin(email: String, password: String) { ... }

fun fetchTodoList() {
runCatching {
fetchTodoUseCase
.fetch()
.collect { result ->
_todoList.update { result }
}
}
}
}
class FooViewModel(
private val loginUseCase: LoginUseCase,
private val userSessionManager: UserSessionManager,
private val fetchTodoUseCase: GetTodoUseCase,
private val insertTodoUseCase: SetTodoUseCase
) : ViewModel() {

private val _login = MutableStateFlow<UserLogin>()


val login get() = _login.asStateFlow()

private val _todoList = MutableStateFlow<TodoList>()


val todoList get() = _todoList.asStateFlow()

fun doLogin(email: String, password: String) { ... }

fun fetchTodoList() { ... }

fun insertTodo(todo: Todo) {


runCatching {
insertTodoUseCase.insert(todo)
.collect { isSucceed ->
if (isSucceed) {
// TODO: Show Toast
}
}
}
}
}
class FooViewModel(
private val loginUseCase: LoginUseCase,
private val userSessionManager: UserSessionManager,
private val fetchTodoUseCase: GetTodoUseCase,
private val insertTodoUseCase: SetTodoUseCase
) : ViewModel() {

private val _login = MutableStateFlow<UserLogin>()


val login get() = _login.asStateFlow()

private val _todoList = MutableStateFlow<TodoList>()


val todoList get() = _todoList.asStateFlow()

fun doLogin(email: String, password: String) { ... }

fun fetchTodoList() { ... }

fun insertTodo(todo: Todo) { ... }


}
Update
After
Framework

Login.update

UI ViewModel

ters
regi
er Todo.update
st
gi
re

ObservableUpdateFactor
y
class FooViewModel(
private val loginUseCase: LoginUseCase,
private val userSessionManager: UserSessionManager,
private val fetchTodoUseCase: GetTodoUseCase,
private val insertTodoUseCase: SetTodoUseCase
) : ViewModel() {

private val _login = MutableStateFlow<UserLogin>()


val login get() = _login.asStateFlow()

private val _todoList = MutableStateFlow<TodoList>()


val todoList get() = _todoList.asStateFlow()

fun doLogin(email: String, password: String) { ... }

fun fetchTodoList() { ... }

fun insertTodo(todo: Todo) { ... }


}
class FooViewModel(
private val login: LoginUpdate
private val fetchTodoUseCase: GetTodoUseCase,
private val insertTodoUseCase: SetTodoUseCase
) : ViewModel() {

private val _todoList = MutableStateFlow<TodoList>()


val todoList get() = _todoList.asStateFlow()

fun fetchTodoList() { ... }

fun insertTodo(todo: Todo) { ... }


}
class FooViewModel(
private val login: LoginUpdate
private val fetchTodoUseCase: GetTodoUseCase,
private val insertTodoUseCase: SetTodoUseCase
) : ViewModel() {

private val _todoList = MutableStateFlow<TodoList>()


val todoList get() = _todoList.asStateFlow()

fun fetchTodoList() { ... }

fun insertTodo(todo: Todo) { ... }


}
class FooViewModel(
private val login: LoginUpdate
private val fetchTodoUseCase: GetTodoUseCase,
private val insertTodoUseCase: SetTodoUseCase
) : ViewModel() {

private val _todoList = MutableStateFlow<TodoList>()


val todoList get() = _todoList.asStateFlow()

fun fetchTodoList() { ... }

fun insertTodo(todo: Todo) { ... }


}
class FooViewModel(
private val login: LoginUpdate
private val todo: TodoUpdate
) : ViewModel()
class FooViewModel(
private val login: LoginUpdate,
private val todo: TodoUpdate,
) : ViewModel()
class FooViewModel(
private val login: LoginUpdate,
private val todo: TodoUpdate,
) : ViewModel() {

private val factory = ObservableUpdateFactory(


eventHandler = eventHandler,
effectHandler = effectHandler,
viewModel = this
)
}
class FooViewModel(
private val login: LoginUpdate,
private val todo: TodoUpdate,
) : ViewModel() {

private val factory = ObservableUpdateFactory(


eventHandler = eventHandler,
effectHandler = effectHandler,
viewModel = this
)

init {
factory.registerUpdates(
login,
todo
)

factory.loop()
}
}
class FooViewModel(
private val login: LoginUpdate,
private val todo: TodoUpdate,
) : ViewModel() {

private val factory = ObservableUpdateFactory(


eventHandler = eventHandler,
effectHandler = effectHandler,
viewModel = this
)

init {
factory.registerUpdates(
login,
todo
)

factory.loop()
}
}
class FooViewModel(
private val login: LoginUpdate,
private val todo: TodoUpdate,
) : ViewModel() {

val state: StateFlow<FooUiState>


get() = combine(login.state, todo.state) { loginState, todoState ->
FooUiState(loginState, todoState)
}
.flowOn(dispatcher.default)
.stateIn(viewModelScope)

private val factory = ObservableUpdateFactory(


eventHandler = eventHandler,
effectHandler = effectHandler,
viewModel = this
)

init {
factory.registerUpdates(
login,
todo
)

factory.loop()
}
}
class FooViewModel(
private val login: LoginUpdate,
private val todo: TodoUpdate,
) : ViewModel() {

val state:
val state:StateFlow<FooUiState>
StateFlow<FooUiState>
get()
get()==combine(login.state,
combine(login.state,todo.state)
todo.state)
{ loginState,
{ loginState,
todoState
todoState
-> ->
FooUiState(loginState,
FooUiState(loginState,todoState)
todoState)
}}
.flowOn(dispatcher.default)
.flowOn(dispatcher.default)
.stateIn(viewModelScope)
.stateIn(viewModelScope)

private val factory = ObservableUpdateFactory(


eventHandler = eventHandler,
effectHandler = effectHandler,
viewModel = this
)

init {
factory.registerUpdates(
factory.registerUpdates(
login,
login,
todo
todo
))

factory.loop()
}
}
Update
Framework

Login.update

UI ViewModel

ters
regi
er Todo.update
st
gi
re

ObservableUpdateFactor
y
Update Scal
Framework e!

Login.update

UI ViewModel

ters
regi
er Todo.update
st
gi
re

ObservableUpdateFactor regist
e r

y ….update
Login.updat
e

First
Define your UiState / Model as respectively
LoginUiState.kt

data class LoginUiState(


val isLoggingIn: Boolean,
val authToken: String,
)
Login.updat
e

Second
Define Event and Effect that Update has
Login.action.kt

// Events
data class LoginClicked(email: String, password: String): Event

// Effects
data object NavigateToHome : Effect
data class ShowErrorMessage(message: String) : Effect
Login.updat
e

Lastly
Create your own Update!
Login.update.kt

interface LoginUpdate : Update {

val state: Flow<LoginUiState>


}
Login.update.kt

class LoginUpdateImpl(
private val loginUseCase: LoginUseCase,
private val userSessionManager: UserSessionManager
) : LoginUpdate {

private val _state = MutableStateFlow<UserLogin>()


override val state get() = _state.asStateFlow()

override fun handleEvent(event: Event) {


when (event) {
is LoginClicked -> doLogin(event.email, event.password)
}
}

private fun doLogin(email: String, password: String) {


launch {
loginUseCase
.request(email, password)
.collect { result ->
userSessionManager.set(result)
_state.update { result }
}
}
}
}
Login.update.kt

class LoginUpdateImpl(
private val loginUseCase: LoginUseCase,
private val userSessionManager: UserSessionManager
) : LoginUpdate {

private val _state = MutableStateFlow<UserLogin>()


override val state get() = _state.asStateFlow()

override fun handleEvent(event: Event) {


when (event) {
is LoginClicked -> doLogin(event.email, event.password)
}
}

private fun doLogin(email: String, password: String) {


launch {
loginUseCase
.request(email, password)
.collect { result ->
userSessionManager.set(result)
_state.update { result }
}
}
}
}
Login.update.kt

class LoginUpdateImpl(
private val loginUseCase: LoginUseCase,
private val userSessionManager: UserSessionManager
) : LoginUpdate {

override fun handleEvent(event: Event) { ... }

override fun handleEffect(effect: Effect) {


when (effect) {
is ShowErrorMessage -> {
globalEffect.send(Toast("Fail to login"))
}
}
}

private fun doLogin(email: String, password: String) {


launch {
loginUseCase
.request(email, password)
.onError { handleEffect(ShowErrorMessage(it)) }
.collect { ... }
}
}

}
Login.update.kt

class LoginUpdateImpl(
private val loginUseCase: LoginUseCase,
private val userSessionManager: UserSessionManager
) : LoginUpdate {

override fun handleEvent(event: Event) { ... }

override fun handleEffect(effect: Effect) {


when (effect) {
is ShowErrorMessage -> {
globalEffect.send(Toast("Fail to login"))
}
}
}

private fun doLogin(email: String, password: String) {


launch {
loginUseCase
.request(email, password)
.onError { handleEffect(ShowErrorMessage(it)) }
.collect { ... }
}
}

}
Login.update.kt

class LoginUpdateImpl(
private val loginUseCase: LoginUseCase,
private val userSessionManager: UserSessionManager
) : LoginUpdate {

override fun handleEvent(event: Event) { ... }

override fun handleEffect(effect: Effect) {


when (effect) {
is ShowErrorMessage -> {
globalEffect.send(Toast("Fail to login"))
}
}
}

private fun doLogin(email: String, password: String) {


launch {
loginUseCase
.request(email, password)
.onError { handleEffect(ShowErrorMessage(it)) }
.collect { ... }
}
}

}
Make Test great again, Unit Test!

Unit
Test

Test your Login.update.kt


class LoginUpdateTest {

@Test
fun doCorrectLoginNavigateToHome() {
// Given
val expected = UserLogin(isSucceed="true", token="foo-bar")
val (email, password) = Pair(
"[email protected]",
"buttagowa"
)

when(loginUseCase.request(email, password)).return(expected)

test<LoginUpdate> {
event(LoginClicked(email, password))
equals(state.isLoggingIn == expected.isSucceed)
isEffect<NavigateToHome>()
}
}
}
Conclusion

TL;DR
- The architecture must be scalable.

- We are in an event-driven era, reflecting user needs.

- The Update Framework reduces complexity while ensuring

scalability (re: extensible).


Quick Demo
Thanks! barakallah fiik
@isfaaghyth

GDG Makassar

You might also like