Android Mvi Pattern Example with Data Flow Reactive
It is responsible for communicating with the true owner of the data (remote server, local database) and returning the domain object.
The Interface should not know who the owner of the Data is.
interface MessageRepository {
suspend fun getMessages(): ImmutableList<Message>
fun getMessageFlow(): Flow<ImmutableList<Message>>
}
class MessageRepositoryImpl @Inject constructor(
// Remote Data Source
private val messageApi: MessageApi,
// Local Data Source
private val messageDao: MessageDao
) : MessageRepository {
override suspend fun getMessages(): ImmutableList<Message> {
val messages = messageApi.getMessages().response
messageDao.upsertMessages()
return messages
}
override fun getMessageFlow(): Flow<ImmutableList<Message>> {
return messageDao.getMessageFlow()
}
}
Performs a single, defined domain function. Receive injections from repositories and other UseCases.
class GetMessagesUseCase @Inject constructor(
private val messsageRepository: MessageRepository
) {
operator suspend fun invoke(): ImmutableList<Message> {
return messageRepository.getMessages()
}
}
Handle state changes for a single domain that integrates multiple features. Inject a UseCase.
From Middleware, this can be screen-dependent.
class MessageMiddleware @Inject constructor(
private val getMessagesUseCase: GetMessagesUseCase
) : Middleware<MessagesState> {
override suspend fun process(currentState: MessagesState, intent: Intent): MessagesState {
return when (intent) {
is ScreenInitIntent -> currentState.reduce(messages = getMessagesUseCase())
else -> currentState
}
}
}
Control the overall behavior for one screen. Middleware is injected.
@HiltViewModel
class MessageListViewModel @Inject(
private val messageMiddleware : MessageMiddleware
) : MviViewModel<MessageListState>() {
override fun produceMiddlewares(): List<Middleware<in MessageListState>> {
return listOf(messageMiddleware)
}
override fun produceInitState(): MessageListState {
return MessageListState()
}
}
@Composable
fun MyMessage(message : String) {
val emitter = LocalIntentEmitter.current
Text(text = message, modifier = Modifier.clickable(onClick = { emitter.dispatch(MessageClickIntent(message)) }))
}
Socket example, we sometimes need to change the state of the screen by means other than direct action by the user in our application.
class MessageMiddleware @Inject constructor(
private val observeMessageSocketUseCase : ObserveMessageSocketUseCase
) : Middleware<MessageState> {
override fun produceIntentFlow() : Flow<Intent> {
return observeMessageSocketUseCase().map { MessageSocketChangedIntent(it) }
}
}