
Facilitates application initialization by defining initializers, managing their execution order, handling errors with customizable renderers, and integrating with splash screens for a seamless startup experience.
:initialization:domain and initialization:ui modules..aar-s created in step 1 to your project.mvi-compose and mvi-core GFT libraries to your dependencies.Define any number of initializers by implementing the Initializer interface.
class SomeInitializer : Initializer {
override suspend fun initialize() {
// do some stuff
}
}⚠
initializemethod will run on the current thread which may be themainthread. UsewithContext(dispatcher)to perform long running operations.
Define the initializers graph.
initializeInParallel.val initializationGraph = listOf(
{ InitializerOne() },
{ InitializerTwo() },
{
initializeInParallel(
{ InitializerThree() },
{ InitializerFour() }
)
},
{ InitializerFive() }
)
// or with Koin
val ApplicationInitializationQualifier = named("ApplicationInitializationQualifier")
factory(ApplicationInitializationQualifier) {
listOf(
{ get<InitializerOne>() },
{ get<InitializerTwo>() },
{
initializeInParallel(
{ get<InitializerThree>() },
{ get<InitializerFour>() }
)
},
{ get<InitializerFive>() }
)
}Define initialization process.
InitializationIdentifier.UseCases you may use InitializationService.defineInitializationProcess directly.val applicationInitializationIdentifier = InitializationIdentifier("ApplicationInitializationIdentifier")
val defineInitializationProcess: DefineInitializationProcessUseCase = ...
defineInitializationProcess(
identifier = ApplicationInitializationIdentifier,
initializers = initializationGraph
)
// or with Koin
defineInitializationProcessUseCase(
identifier = ApplicationInitializationIdentifier,
initializers = get(ApplicationInitializationQualifier)
)Define any number of error renderers.
class VerySpecificErrorRenderer(
private val restartApplication: RestartApplicationUseCase,
) : InitializationErrorRenderer {
@Composable
override fun RenderError(error: Throwable) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Oh, no!\n Very specific error happened!",
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
restartApplication()
}
) {
Text(text = "Try again")
}
}
}
override fun canRenderError(error: Throwable): Boolean = error is VerySpecificError
}InitializationRenderer with fun canRenderError(error: Throwable).RenderError is just a common @Composable method. We suggest following MVI pattern while implementing error renderers.ℹ The presented error renderer simply restarts the whole application when user clicks "Try again" button.
However, one may retry just the initialization by callingStartInitializationUseCaseagain with the same identifier.
Define the order of error renderers by implementing InitializationErrorRenderer interface.
Everytime initialization fails, the error renders list is searched for an appropriate renderer -
the first error renderer that returns true from fun canRenderError(error: Throwable): Boolean is chosen.
val applicationInitializationErrorRenderers = {
listOf(
VerySpecificErrorRenderer(),
GeneralErrorRenderer()
)
}
// or with Koin
val ApplicationInitializationErrorRenderers = named("AppInitializationErrorRenderers")
factory(qualifier = ApplicationInitializationErrorRenderers) {
{
listOf(
get<VerySpecificErrorRenderer>(),
get<GeneralErrorRenderer>()
)
}
}ℹ Note that initialization renderers list is created in a lazy fashion.
Show application content depending on the initialization status.
Initialize(
initializationIdentifier = ApplicationInitializationIdentifier,
errorRenderersProvider = applicationInitializationErrorRenderers,
) {
// application content
}ℹ Alternatively you may show you custom initialization progress renderer. Check the Custom initialization progress indicator section below for more details.
Start initialization process.
There are two ways of staring initialization process:
StartInitializationUseCase (or InitializationService.initialize)Initialize composable method enters the Composition.If you want to keep the splash screen until the initialization is complete you need to use setKeepOnScreenCondition.
class MainActivity : ComponentActivity(), KoinComponent {
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen().setKeepOnScreenCondition {
val initState = get<StreamInitializationStateUseCase>()(ApplicationInitializationIdentifier).value
initState != InitState.Initialized && initState !is InitState.Failed
}
super.onCreate(savedInstanceState)
...
Initialize(...) {
}
...
}
}⚠ You still have to use
Initializecomposable in your composition to render initialization errors and/or start initialization automatically.
Whenever you want to control on your own whether application content is displayed you may use StreamInitializationStateUseCase
to track the initialization progress.
You need to:
data class CustomSplashScreenViewState(
internal val isLoadingIndicatorVisible: Boolean,
internal val isContentVisible: Boolean,
) : ViewState@Composable
fun CustomSplashScreen(
initializationIdentifier: InitializationIdentifier,
viewModel: MviViewModel<CustomSplashScreenViewState, ViewEvent, NavigationEffect, ViewEffect> = ...,
content: @Composable () -> Unit,
) {
ViewState(viewModel) {
if (viewState.isContentVisible) {
content()
}
if (viewState.isLoadingIndicatorVisible) {
Dialog(onDismissRequest = { }) {
Box(
modifier = Modifier.fillMaxSize()
) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
}
}
}
}
}class CustomSplashScreenViewModel(
initializationIdentifier: InitializationIdentifier,
streamInitializationState: StreamInitializationStateUseCase,
) : BaseMviViewModel<CustomSplashScreenViewState, ViewEvent, NavigationEffect, ViewEffect>() {
override val viewStates: StateFlow<CustomSplashScreenViewState> = streamInitializationState(initializationIdentifier)
.map { state ->
state.toViewState()
}
.toViewStates(
streamInitializationState(initializationIdentifier).value.toViewState(),
viewModelScope
)
override fun onEvent(event: ViewEvent) = Unit
private fun InitState.toViewState() = CustomSplashScreenViewState(
isLoadingIndicatorVisible = this != Initialized,
isContentVisible = this == Initialized
)
}content parameter of the Initialize method.showContentDuringInitialization parameter of Initialize composable to true
Initialize(
initializationIdentifier = ApplicationInitializationIdentifier,
showContentDuringInitialization = true,
errorRenderersProvider = get(AppInitializationErrorRenderers)
) {
CustomSplashScreen(
initializationIdentifier = ApplicationInitializationIdentifier
) {
ApplicationContent(
modifier = Modifier.padding(innerPadding)
)
}
}:initialization:domain and initialization:ui modules..aar-s created in step 1 to your project.mvi-compose and mvi-core GFT libraries to your dependencies.Define any number of initializers by implementing the Initializer interface.
class SomeInitializer : Initializer {
override suspend fun initialize() {
// do some stuff
}
}⚠
initializemethod will run on the current thread which may be themainthread. UsewithContext(dispatcher)to perform long running operations.
Define the initializers graph.
initializeInParallel.val initializationGraph = listOf(
{ InitializerOne() },
{ InitializerTwo() },
{
initializeInParallel(
{ InitializerThree() },
{ InitializerFour() }
)
},
{ InitializerFive() }
)
// or with Koin
val ApplicationInitializationQualifier = named("ApplicationInitializationQualifier")
factory(ApplicationInitializationQualifier) {
listOf(
{ get<InitializerOne>() },
{ get<InitializerTwo>() },
{
initializeInParallel(
{ get<InitializerThree>() },
{ get<InitializerFour>() }
)
},
{ get<InitializerFive>() }
)
}Define initialization process.
InitializationIdentifier.UseCases you may use InitializationService.defineInitializationProcess directly.val applicationInitializationIdentifier = InitializationIdentifier("ApplicationInitializationIdentifier")
val defineInitializationProcess: DefineInitializationProcessUseCase = ...
defineInitializationProcess(
identifier = ApplicationInitializationIdentifier,
initializers = initializationGraph
)
// or with Koin
defineInitializationProcessUseCase(
identifier = ApplicationInitializationIdentifier,
initializers = get(ApplicationInitializationQualifier)
)Define any number of error renderers.
class VerySpecificErrorRenderer(
private val restartApplication: RestartApplicationUseCase,
) : InitializationErrorRenderer {
@Composable
override fun RenderError(error: Throwable) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Oh, no!\n Very specific error happened!",
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
restartApplication()
}
) {
Text(text = "Try again")
}
}
}
override fun canRenderError(error: Throwable): Boolean = error is VerySpecificError
}InitializationRenderer with fun canRenderError(error: Throwable).RenderError is just a common @Composable method. We suggest following MVI pattern while implementing error renderers.ℹ The presented error renderer simply restarts the whole application when user clicks "Try again" button.
However, one may retry just the initialization by callingStartInitializationUseCaseagain with the same identifier.
Define the order of error renderers by implementing InitializationErrorRenderer interface.
Everytime initialization fails, the error renders list is searched for an appropriate renderer -
the first error renderer that returns true from fun canRenderError(error: Throwable): Boolean is chosen.
val applicationInitializationErrorRenderers = {
listOf(
VerySpecificErrorRenderer(),
GeneralErrorRenderer()
)
}
// or with Koin
val ApplicationInitializationErrorRenderers = named("AppInitializationErrorRenderers")
factory(qualifier = ApplicationInitializationErrorRenderers) {
{
listOf(
get<VerySpecificErrorRenderer>(),
get<GeneralErrorRenderer>()
)
}
}ℹ Note that initialization renderers list is created in a lazy fashion.
Show application content depending on the initialization status.
Initialize(
initializationIdentifier = ApplicationInitializationIdentifier,
errorRenderersProvider = applicationInitializationErrorRenderers,
) {
// application content
}ℹ Alternatively you may show you custom initialization progress renderer. Check the Custom initialization progress indicator section below for more details.
Start initialization process.
There are two ways of staring initialization process:
StartInitializationUseCase (or InitializationService.initialize)Initialize composable method enters the Composition.If you want to keep the splash screen until the initialization is complete you need to use setKeepOnScreenCondition.
class MainActivity : ComponentActivity(), KoinComponent {
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen().setKeepOnScreenCondition {
val initState = get<StreamInitializationStateUseCase>()(ApplicationInitializationIdentifier).value
initState != InitState.Initialized && initState !is InitState.Failed
}
super.onCreate(savedInstanceState)
...
Initialize(...) {
}
...
}
}⚠ You still have to use
Initializecomposable in your composition to render initialization errors and/or start initialization automatically.
Whenever you want to control on your own whether application content is displayed you may use StreamInitializationStateUseCase
to track the initialization progress.
You need to:
data class CustomSplashScreenViewState(
internal val isLoadingIndicatorVisible: Boolean,
internal val isContentVisible: Boolean,
) : ViewState@Composable
fun CustomSplashScreen(
initializationIdentifier: InitializationIdentifier,
viewModel: MviViewModel<CustomSplashScreenViewState, ViewEvent, NavigationEffect, ViewEffect> = ...,
content: @Composable () -> Unit,
) {
ViewState(viewModel) {
if (viewState.isContentVisible) {
content()
}
if (viewState.isLoadingIndicatorVisible) {
Dialog(onDismissRequest = { }) {
Box(
modifier = Modifier.fillMaxSize()
) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
}
}
}
}
}class CustomSplashScreenViewModel(
initializationIdentifier: InitializationIdentifier,
streamInitializationState: StreamInitializationStateUseCase,
) : BaseMviViewModel<CustomSplashScreenViewState, ViewEvent, NavigationEffect, ViewEffect>() {
override val viewStates: StateFlow<CustomSplashScreenViewState> = streamInitializationState(initializationIdentifier)
.map { state ->
state.toViewState()
}
.toViewStates(
streamInitializationState(initializationIdentifier).value.toViewState(),
viewModelScope
)
override fun onEvent(event: ViewEvent) = Unit
private fun InitState.toViewState() = CustomSplashScreenViewState(
isLoadingIndicatorVisible = this != Initialized,
isContentVisible = this == Initialized
)
}content parameter of the Initialize method.showContentDuringInitialization parameter of Initialize composable to true
Initialize(
initializationIdentifier = ApplicationInitializationIdentifier,
showContentDuringInitialization = true,
errorRenderersProvider = get(AppInitializationErrorRenderers)
) {
CustomSplashScreen(
initializationIdentifier = ApplicationInitializationIdentifier
) {
ApplicationContent(
modifier = Modifier.padding(innerPadding)
)
}
}