Last active
December 6, 2024 12:49
-
-
Save Matikano/2eeea28f76a823aaf6893e7505c7a69d to your computer and use it in GitHub Desktop.
Android Studio usefull MVVM LiveTemplates
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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