Skip to content

Commit d5c23cd

Browse files
committed
Refactor: Extract MoviesApplication composable
This commit extracts the main navigation logic from `MainActivity` into a new `MoviesApplication` composable. Key changes: - A new `MoviesApplication.kt` file is created to house the `NavDisplay` and its configuration. - `MainActivity` is simplified to just call `MoviesApplication`. - The `observeIn` extension function is removed in favor of `flowWithLifecycle`. - Modules (`TheMovieDbModule`, `MovieDetailsModule`) now own the `actions` `Channel`, removing redundant `remember` calls in the UI. - Dependencies like `AppModule` and `BackStack` are now passed explicitly to screen composables (`MoviesNavScreen`, `MovieDetailsNavScreen`). - Introduced a `BackStack` typealias for `SnapshotStateList<Screen>`.
1 parent d38c3de commit d5c23cd

File tree

7 files changed

+83
-78
lines changed

7 files changed

+83
-78
lines changed

app/src/main/java/io/github/lordraydenmk/themoviedbapp/MainActivity.kt

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,14 @@ import android.os.Bundle
44
import androidx.activity.compose.setContent
55
import androidx.activity.enableEdgeToEdge
66
import androidx.appcompat.app.AppCompatActivity
7-
import androidx.compose.material3.ExperimentalMaterial3Api
8-
import androidx.compose.runtime.mutableStateListOf
9-
import androidx.compose.runtime.remember
10-
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
11-
import androidx.navigation3.runtime.NavEntry
12-
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
13-
import androidx.navigation3.ui.NavDisplay
14-
import io.github.lordraydenmk.themoviedbapp.movies.Screen
15-
import io.github.lordraydenmk.themoviedbapp.movies.Screen.MovieDetails
16-
import io.github.lordraydenmk.themoviedbapp.movies.Screen.PopularMovies
17-
import io.github.lordraydenmk.themoviedbapp.movies.moviedetails.MovieDetailsNavScreen
18-
import io.github.lordraydenmk.themoviedbapp.movies.popularmovies.MoviesNavScreen
197

20-
@OptIn(ExperimentalMaterial3Api::class)
218
class MainActivity : AppCompatActivity() {
229

2310
override fun onCreate(savedInstanceState: Bundle?) {
2411
enableEdgeToEdge()
2512
super.onCreate(savedInstanceState)
2613
setContent {
27-
val backStack = remember { mutableStateListOf<Screen>(PopularMovies) }
28-
29-
NavDisplay(
30-
entryDecorators = listOf(
31-
rememberSaveableStateHolderNavEntryDecorator(),
32-
rememberViewModelStoreNavEntryDecorator()
33-
),
34-
backStack = backStack,
35-
onBack = { backStack.removeLastOrNull() },
36-
entryProvider = { key: Screen ->
37-
when (key) {
38-
PopularMovies -> NavEntry(key) {
39-
MoviesNavScreen(backStack)
40-
}
41-
42-
is MovieDetails -> NavEntry(key) {
43-
MovieDetailsNavScreen(key.movieId, backStack)
44-
}
45-
}
46-
}
47-
)
14+
MoviesApplication(appModule())
4815
}
4916
}
5017
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.github.lordraydenmk.themoviedbapp
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.mutableStateListOf
5+
import androidx.compose.runtime.remember
6+
import androidx.compose.runtime.snapshots.SnapshotStateList
7+
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
8+
import androidx.navigation3.runtime.NavEntry
9+
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
10+
import androidx.navigation3.ui.NavDisplay
11+
import io.github.lordraydenmk.themoviedbapp.movies.Screen
12+
import io.github.lordraydenmk.themoviedbapp.movies.Screen.MovieDetails
13+
import io.github.lordraydenmk.themoviedbapp.movies.Screen.PopularMovies
14+
import io.github.lordraydenmk.themoviedbapp.movies.moviedetails.MovieDetailsNavScreen
15+
import io.github.lordraydenmk.themoviedbapp.movies.popularmovies.MoviesNavScreen
16+
17+
typealias BackStack = SnapshotStateList<Screen>
18+
19+
@Composable
20+
fun MoviesApplication(appModule: AppModule) {
21+
val backStack = remember { mutableStateListOf<Screen>(PopularMovies) }
22+
23+
NavDisplay(
24+
entryDecorators = listOf(
25+
rememberSaveableStateHolderNavEntryDecorator(),
26+
rememberViewModelStoreNavEntryDecorator()
27+
),
28+
backStack = backStack,
29+
onBack = { backStack.removeLastOrNull() },
30+
entryProvider = { key: Screen ->
31+
when (key) {
32+
PopularMovies -> NavEntry(key) {
33+
MoviesNavScreen(appModule, backStack)
34+
}
35+
36+
is MovieDetails -> NavEntry(key) {
37+
MovieDetailsNavScreen(appModule, key.movieId, backStack)
38+
}
39+
}
40+
}
41+
)
42+
}

app/src/main/java/io/github/lordraydenmk/themoviedbapp/common/flow.kt

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package io.github.lordraydenmk.themoviedbapp.common
22

3-
import androidx.lifecycle.Lifecycle
4-
import androidx.lifecycle.LifecycleOwner
5-
import androidx.lifecycle.lifecycleScope
6-
import androidx.lifecycle.repeatOnLifecycle
73
import kotlinx.coroutines.CoroutineDispatcher
84
import kotlinx.coroutines.CoroutineScope
95
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -21,7 +17,6 @@ import kotlinx.coroutines.flow.flowOf
2117
import kotlinx.coroutines.flow.flowOn
2218
import kotlinx.coroutines.flow.launchIn
2319
import kotlinx.coroutines.flow.onEach
24-
import kotlinx.coroutines.launch
2520
import kotlin.coroutines.CoroutineContext
2621
import kotlin.coroutines.EmptyCoroutineContext
2722

@@ -65,13 +60,3 @@ suspend inline fun <A, B, C> parZip(
6560
@Suppress("UNCHECKED_CAST")
6661
f(a as A, b as B)
6762
}
68-
69-
fun <A> Flow<A>.observeIn(
70-
lifecycleOwner: LifecycleOwner,
71-
state: Lifecycle.State = Lifecycle.State.STARTED
72-
): Job =
73-
lifecycleOwner.lifecycleScope.launch {
74-
lifecycleOwner.lifecycle.repeatOnLifecycle(state) {
75-
collect()
76-
}
77-
}

app/src/main/java/io/github/lordraydenmk/themoviedbapp/movies/moviedetails/MovieDetailsNavScreen.kt

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,36 @@ import androidx.compose.runtime.LaunchedEffect
55
import androidx.compose.runtime.collectAsState
66
import androidx.compose.runtime.getValue
77
import androidx.compose.runtime.remember
8-
import androidx.compose.runtime.snapshots.SnapshotStateList
9-
import androidx.compose.ui.platform.LocalContext
108
import androidx.lifecycle.Lifecycle
119
import androidx.lifecycle.compose.LocalLifecycleOwner
10+
import androidx.lifecycle.flowWithLifecycle
1211
import androidx.lifecycle.repeatOnLifecycle
1312
import androidx.lifecycle.viewmodel.compose.viewModel
1413
import io.github.lordraydenmk.themoviedbapp.AppModule
15-
import io.github.lordraydenmk.themoviedbapp.appModule
16-
import io.github.lordraydenmk.themoviedbapp.common.observeIn
14+
import io.github.lordraydenmk.themoviedbapp.BackStack
1715
import io.github.lordraydenmk.themoviedbapp.common.presentation.ViewModelAlgebra
18-
import io.github.lordraydenmk.themoviedbapp.movies.Screen
1916
import io.github.lordraydenmk.themoviedbapp.movies.domain.MovieId
2017
import kotlinx.coroutines.channels.Channel
18+
import kotlinx.coroutines.flow.collect
2119
import kotlinx.coroutines.flow.map
2220
import kotlinx.coroutines.flow.receiveAsFlow
2321

2422
@Composable
2523
fun MovieDetailsNavScreen(
24+
appModule: AppModule,
2625
movieId: MovieId,
27-
backStack: SnapshotStateList<Screen>,
26+
backStack: BackStack,
2827
viewModel: MovieDetailsViewModel = viewModel()
2928
) {
30-
val module = object : MovieDetailsModule,
31-
AppModule by LocalContext.current.appModule(),
32-
ViewModelAlgebra<MovieDetailsViewState, MovieDetailsEffect> by viewModel {}
29+
val module = remember {
30+
object : MovieDetailsModule,
31+
AppModule by appModule,
32+
ViewModelAlgebra<MovieDetailsViewState, MovieDetailsEffect> by viewModel {
33+
override val actions: Channel<MovieDetailsAction> = Channel(Channel.UNLIMITED)
34+
}
35+
}
3336

3437

35-
val actions = remember { Channel<MovieDetailsAction>(Channel.UNLIMITED) }
3638
with(module) {
3739
val lifecycleOwner = LocalLifecycleOwner.current
3840
LaunchedEffect(lifecycleOwner) {
@@ -41,13 +43,15 @@ fun MovieDetailsNavScreen(
4143
}
4244
}
4345
LaunchedEffect(lifecycleOwner) {
44-
viewModel.effects.map { effect ->
45-
when (effect) {
46-
is NavigateUp -> backStack.removeLastOrNull()
47-
}
48-
}.observeIn(lifecycleOwner)
46+
viewModel.effects
47+
.flowWithLifecycle(lifecycleOwner.lifecycle)
48+
.map { effect ->
49+
when (effect) {
50+
is NavigateUp -> backStack.removeLastOrNull()
51+
}
52+
}.collect()
4953
}
5054
}
5155
val state by viewModel.viewState.collectAsState(Loading)
52-
MovieDetailsScreen(state, movieId, actions)
56+
MovieDetailsScreen(state, movieId, module.actions)
5357
}

app/src/main/java/io/github/lordraydenmk/themoviedbapp/movies/moviedetails/movieDetails.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ import io.github.lordraydenmk.themoviedbapp.movies.data.movieDetails
1414
import io.github.lordraydenmk.themoviedbapp.movies.domain.Movie
1515
import io.github.lordraydenmk.themoviedbapp.movies.domain.MovieId
1616
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.channels.Channel
1718
import kotlinx.coroutines.flow.Flow
1819
import kotlinx.coroutines.flow.map
1920

2021
interface MovieDetailsModule : AppModule,
21-
ViewModelAlgebra<MovieDetailsViewState, MovieDetailsEffect>
22+
ViewModelAlgebra<MovieDetailsViewState, MovieDetailsEffect> {
23+
val actions: Channel<MovieDetailsAction>
24+
}
2225

2326
suspend fun MovieDetailsModule.program(
2427
movieId: MovieId,

app/src/main/java/io/github/lordraydenmk/themoviedbapp/movies/popularmovies/MoviesNavScreen.kt

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@ import androidx.compose.runtime.LaunchedEffect
66
import androidx.compose.runtime.collectAsState
77
import androidx.compose.runtime.getValue
88
import androidx.compose.runtime.remember
9-
import androidx.compose.runtime.snapshots.SnapshotStateList
10-
import androidx.compose.ui.platform.LocalContext
119
import androidx.lifecycle.Lifecycle
1210
import androidx.lifecycle.compose.LocalLifecycleOwner
1311
import androidx.lifecycle.flowWithLifecycle
1412
import androidx.lifecycle.repeatOnLifecycle
1513
import androidx.lifecycle.viewmodel.compose.viewModel
1614
import io.github.lordraydenmk.themoviedbapp.AppModule
17-
import io.github.lordraydenmk.themoviedbapp.appModule
15+
import io.github.lordraydenmk.themoviedbapp.BackStack
1816
import io.github.lordraydenmk.themoviedbapp.common.presentation.ViewModelAlgebra
1917
import io.github.lordraydenmk.themoviedbapp.movies.Screen
2018
import kotlinx.coroutines.channels.Channel
@@ -25,14 +23,17 @@ import kotlinx.coroutines.flow.receiveAsFlow
2523
@OptIn(ExperimentalMaterial3Api::class)
2624
@Composable
2725
fun MoviesNavScreen(
28-
backStack: SnapshotStateList<Screen>,
26+
appModule: AppModule,
27+
backStack: BackStack,
2928
viewModel: MoviesViewModel = viewModel()
3029
) {
31-
val module = object : TheMovieDbModule,
32-
AppModule by LocalContext.current.appModule(),
33-
ViewModelAlgebra<PopularMoviesViewState, MoviesEffect> by viewModel {}
34-
35-
val actions = remember { Channel<MoviesAction>(Channel.UNLIMITED) }
30+
val module = remember {
31+
object : TheMovieDbModule,
32+
AppModule by appModule,
33+
ViewModelAlgebra<PopularMoviesViewState, MoviesEffect> by viewModel {
34+
override val actions: Channel<MoviesAction> = Channel(Channel.UNLIMITED)
35+
}
36+
}
3637

3738
with(module) {
3839
val lifecycleOwner = LocalLifecycleOwner.current
@@ -43,7 +44,7 @@ fun MoviesNavScreen(
4344
}
4445
LaunchedEffect(lifecycleOwner) {
4546
viewModel.effects
46-
.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
47+
.flowWithLifecycle(lifecycleOwner.lifecycle)
4748
.map { effect ->
4849
when (effect) {
4950
is NavigateToDetails -> backStack.add(Screen.MovieDetails(effect.movieId))
@@ -53,5 +54,5 @@ fun MoviesNavScreen(
5354
}
5455

5556
val state by viewModel.viewState.collectAsState(Loading)
56-
PopularMoviesScreen(state = state, actions)
57+
PopularMoviesScreen(state = state, module.actions)
5758
}

app/src/main/java/io/github/lordraydenmk/themoviedbapp/movies/popularmovies/moviesList.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ import io.github.lordraydenmk.themoviedbapp.movies.NetworkError
1111
import io.github.lordraydenmk.themoviedbapp.movies.ServerError
1212
import io.github.lordraydenmk.themoviedbapp.movies.data.popularMovies
1313
import kotlinx.coroutines.Dispatchers
14+
import kotlinx.coroutines.channels.Channel
1415
import kotlinx.coroutines.flow.Flow
1516
import kotlinx.coroutines.flow.map
1617

17-
interface TheMovieDbModule : AppModule, ViewModelAlgebra<PopularMoviesViewState, MoviesEffect>
18+
interface TheMovieDbModule : AppModule, ViewModelAlgebra<PopularMoviesViewState, MoviesEffect> {
19+
val actions: Channel<MoviesAction>
20+
}
1821

1922
suspend fun TheMovieDbModule.program(actions: Flow<MoviesAction>): Unit =
2023
parZip(Dispatchers.Default, { firstLoad() }, { handleActions(actions) })

0 commit comments

Comments
 (0)