Skip to content

Instantly share code, notes, and snippets.

@Matikano
Last active December 6, 2024 12:49
Show Gist options
  • Save Matikano/2eeea28f76a823aaf6893e7505c7a69d to your computer and use it in GitHub Desktop.
Save Matikano/2eeea28f76a823aaf6893e7505c7a69d to your computer and use it in GitHub Desktop.
Android Studio usefull MVVM LiveTemplates
1. MVI BaseViewModel (mviBaseVm) -Creates an abstract class for MVI BaseViewModel; Reuqires UiAction and UiEvent marker interfaces:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
abstract class BaseViewModel<STATE, ACTION: UiAction>(
val initialState: STATE
): ViewModel() {
open val stateFlowSubscriptionStopTimeoutMillis = 5000L
private val _uiState: MutableStateFlow<STATE> by lazy {
MutableStateFlow(initialState)
}
val uiState = _uiState
.onStart { loadData() }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stateFlowSubscriptionStopTimeoutMillis),
initialValue = initialState
)
private val _uiEvent = Channel<UiEvent>()
val uiEvent = _uiEvent.receiveAsFlow()
val currentState: STATE
get() = uiState.value
val actionHandler: (ACTION) -> Unit = ::onAction
abstract fun loadData()
abstract fun onAction(action: ACTION)
fun updateUiState(updateLambda: (STATE) -> STATE) {
_uiState.update(updateLambda)
}
suspend fun sendUiEvent(event: UiEvent) = _uiEvent.send(event)
}
2. MVI Contract (mviContract) - Creates contract object for MVI design pattern:
object $SCREEN_NAME$Contract {
sealed interface Action: UiAction {
//TODO: Implement screen UiActions here (user actions)
}
data class State(
// this is stub value, remove if not needed
val isLoading: Boolean = true,
//TODO: Implement your State data here
)
sealed interface Event: UiEvent {
//TODO: Implement your UiEvents here (one-time Ui events)
}
}
3. MVI ViewModel (mviVm) - Creates MVI ViewModel that implements BaseViewModel - requires Contract object in the same package:
import $PACKAGE_NAME$.$SCREEN_NAME$Contract.Action
import $PACKAGE_NAME$.$SCREEN_NAME$Contract.State
class $SCREEN_NAME$ViewModel(
//TODO: add ViewModel dependencies here (use cases/repositories)
): BaseViewModel<State, Action>(
initialState = State()
) {
override fun loadData() {
//TODO: initialize your data here or do nothing if not needed
}
override fun onAction(action: Action) {
when (action) {
//TODO: handle you UI Actions here
}
}
}
4. UseCase (usecase):
import javax.inject.Inject
class $NAME$UseCase @Inject constructor(
private val $DEPENDENCY$: $DEPENDENCY_TYPE$
) {
suspend operator fun invoke($ARG$: $ARG_TYPE$): $RETURN_TYPE$ {
$FUNCTION_BODY$
}
}
5. Result template (resultTemplate) - Creates Result generic type with utility functions:
typealias DomainError = Error
typealias EmptyResult<E> = Result<Unit, E>
sealed interface Result<out D, out E: Error> {
data class Success<out D>(val data: D): Result<D, Nothing>
data class Error<out E: DomainError>(val error: E): Result<Nothing, E>
}
inline fun <T, E: Error, R> Result<T, E>.map(map: (T) -> R): Result<R, E> =
when(this) {
is Result.Error -> Result.Error(error)
is Result.Success -> Result.Success(map(data))
}
fun <T, E: Error> Result<T, E>.asEmptyDataResult(): EmptyResult<E> = map {}
inline fun <T, E: Error> Result<T, E>.onSuccess(action: (T) -> Unit): Result<T, E> =
when(this) {
is Result.Error -> this
is Result.Success -> {
action(data)
this
}
}
inline fun <T, E: Error> Result<T, E>.onError(action: (E) -> Unit): Result<T, E> =
when(this) {
is Result.Error -> {
action(error)
this
}
is Result.Success -> this
}
6. Ktor Networking Utils (ktorNetworkUtils) - Creates utility functions for ktor HttpResponse:
import io.ktor.client.statement.HttpResponse
import io.ktor.util.network.UnresolvedAddressException
import kotlinx.coroutines.ensureActive
import kotlinx.serialization.SerializationException
import kotlin.coroutines.coroutineContext
import io.ktor.client.call.NoTransformationFoundException
import io.ktor.client.call.body
val HTTP_RESPONSE_SUCCESS_CODES = (200..299)
val HTTP_RESPONSE_SERVER_ERROR_CODES = (500..599)
suspend inline fun <reified T> responseToResult(
response: HttpResponse
): Result<T, NetworkError> =
when(response.status.value) {
in HTTP_RESPONSE_SUCCESS_CODES -> {
try {
Result.Success(response.body<T>())
} catch(e: NoTransformationFoundException) {
Result.Error(NetworkError.SERIALIZATION)
}
}
408 -> Result.Error(NetworkError.REQUEST_TIMEOUT)
429 -> Result.Error(NetworkError.TOO_MANY_REQUESTS)
in HTTP_RESPONSE_SERVER_ERROR_CODES -> Result.Error(NetworkError.SERVER_ERROR)
else -> Result.Error(NetworkError.UNKNOWN)
}
suspend inline fun <reified T> safeCall(
execute: () -> HttpResponse
): Result<T, NetworkError> =
try {
responseToResult(execute())
} catch(e: UnresolvedAddressException) {
Result.Error(NetworkError.NO_INTERNET)
} catch(e: SerializationException) {
Result.Error(NetworkError.SERIALIZATION)
} catch(e: Exception) {
coroutineContext.ensureActive()
Result.Error(NetworkError.UNKNOWN)
}
7. UiText (uiText) - Creates UiText sealed interface for Compose and classic Context aware views:
import android.content.Context
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
sealed interface UiText {
data class DynamicString(val value: String): UiText
class StringResourceId(
@StringRes val id: Int,
val args: Array<Any> = arrayOf()
): UiText
@Composable
fun asString(): String =
when(this) {
is DynamicString -> value
is StringResourceId -> stringResource(id, args)
}
fun asString(context: Context): String =
when(this) {
is DynamicString -> value
is StringResourceId -> context.getString(id, *args)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment