From dd0cc9ba2f3d8804ab73431299fa5a310b61c555 Mon Sep 17 00:00:00 2001 From: "Z. Charles Dziura" Date: Thu, 31 Oct 2024 23:48:14 -0400 Subject: [PATCH] I think I got navigation working? --- .../debtpirate/app/host/MainActivity.kt | 59 +++++++++++++++---- .../debtpirate/auth/di/AuthDiModule.kt | 12 ++-- .../domain/repository/AuthRepositoryImpl.kt | 2 +- .../auth/navigation/AuthNavigationEvent.kt | 6 -- .../auth/presentation/login/LoginScreen.kt | 25 ++++---- .../login/LoginScreenViewModel.kt | 12 ++-- .../debtpirate/di/NavigatorModule.kt | 19 ++++++ .../debtpirate/navigation/Destination.kt | 16 +++++ .../debtpirate/navigation/NavigationAction.kt | 13 ++++ .../debtpirate/navigation/Navigator.kt | 34 +++++++++++ 10 files changed, 153 insertions(+), 45 deletions(-) delete mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/navigation/AuthNavigationEvent.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/NavigatorModule.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Destination.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/NavigationAction.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Navigator.kt diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt index a54097d..cd72eea 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt @@ -4,36 +4,71 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import androidx.navigation.compose.navigation import androidx.navigation.compose.rememberNavController import dagger.hilt.android.AndroidEntryPoint -import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme import ing.bikeshedengineer.debtpirate.auth.presentation.login.LoginScreen import ing.bikeshedengineer.debtpirate.auth.presentation.register.RegistrationScreen -import kotlinx.serialization.Serializable - -@Serializable -object LoginRoute - -@Serializable -object RegistrationRoute +import ing.bikeshedengineer.debtpirate.navigation.Destination +import ing.bikeshedengineer.debtpirate.navigation.NavigationAction +import ing.bikeshedengineer.debtpirate.navigation.Navigator +import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import javax.inject.Inject @AndroidEntryPoint class MainActivity : ComponentActivity() { + @Inject + lateinit var navigator: Navigator + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() - setContent { val navController = rememberNavController() + ObserveAsEvents(navigator.navigationActions) { action -> + when (action) { + is NavigationAction.Navigate -> { + navController.navigate(action.destination) { + action.navOptions(this) + } + } + NavigationAction.NavigateUp -> { + navController.navigateUp() + } + } + } + DebtPirateTheme { - NavHost(navController = navController, startDestination = LoginRoute) { - composable() { LoginScreen(navController = navController) } - composable() { RegistrationScreen() } + NavHost(navController = navController, startDestination = Destination.AuthGraph) { + navigation(startDestination = Destination.LoginScreen) { + composable() { LoginScreen(navController = navController) } + composable() { RegistrationScreen() } + } } } } } +} + +@Composable +private fun ObserveAsEvents(flow: Flow, onEvent: (T) -> Unit) { + val lifecycleOwner = LocalLifecycleOwner.current + LaunchedEffect(lifecycleOwner.lifecycle) { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + withContext(Dispatchers.Main.immediate) { + flow.collect(onEvent) + } + } + } } \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/di/AuthDiModule.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/di/AuthDiModule.kt index 7788614..38aa071 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/di/AuthDiModule.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/di/AuthDiModule.kt @@ -3,31 +3,31 @@ package ing.bikeshedengineer.debtpirate.auth.di import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent +import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.android.scopes.ViewModelScoped import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepository import ing.bikeshedengineer.debtpirate.auth.domain.repository.AuthRepositoryImpl import ing.bikeshedengineer.debtpirate.auth.domain.usecase.SubmitLoginCredentialsUseCase import ing.bikeshedengineer.debtpirate.auth.domain.usecase.ValidateLoginCredentialsUseCase import retrofit2.Retrofit -import javax.inject.Singleton @Module -@InstallIn(SingletonComponent::class) +@InstallIn(ViewModelComponent::class) object AuthDiModule { @Provides - @Singleton + @ViewModelScoped fun provideAuthRepository(httpClient: Retrofit): AuthRepository { return AuthRepositoryImpl(httpClient) } @Provides - @Singleton + @ViewModelScoped fun provideSubmitLoginCredentialsUseCase(authRepository: AuthRepository): SubmitLoginCredentialsUseCase { return SubmitLoginCredentialsUseCase(authRepository) } @Provides - @Singleton + @ViewModelScoped fun provideValidateLoginCredentialsUseCase() = ValidateLoginCredentialsUseCase() } \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/repository/AuthRepositoryImpl.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/repository/AuthRepositoryImpl.kt index 8c0fc4f..93c217b 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/repository/AuthRepositoryImpl.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/repository/AuthRepositoryImpl.kt @@ -3,10 +3,10 @@ package ing.bikeshedengineer.debtpirate.auth.domain.repository import com.google.gson.Gson import com.google.gson.reflect.TypeToken import ing.bikeshedengineer.debtpirate.auth.data.remote.endpoint.AuthEndpoint -import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginResponse import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepository +import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import retrofit2.Retrofit diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/navigation/AuthNavigationEvent.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/navigation/AuthNavigationEvent.kt deleted file mode 100644 index f924fe9..0000000 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/navigation/AuthNavigationEvent.kt +++ /dev/null @@ -1,6 +0,0 @@ -package ing.bikeshedengineer.debtpirate.auth.navigation - -sealed class AuthNavigationEvent { - object NavigateToRegistrationScreen : AuthNavigationEvent() - object NavigateToLoginScreen : AuthNavigationEvent() -} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt index c957222..172d5cd 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt @@ -25,7 +25,6 @@ import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment @@ -42,8 +41,6 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import ing.bikeshedengineer.debtpirate.R -import ing.bikeshedengineer.debtpirate.app.host.RegistrationRoute -import ing.bikeshedengineer.debtpirate.auth.navigation.AuthNavigationEvent @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable @@ -51,17 +48,17 @@ fun LoginScreen( navController: NavController, viewModel: LoginScreenViewModel = hiltViewModel() ) { - LaunchedEffect(viewModel.navigationEvent) { - viewModel.navigationEvent.collect { event -> - when (event) { - is AuthNavigationEvent.NavigateToRegistrationScreen -> { - navController.navigate(RegistrationRoute) - } - - else -> {} - } - } - } +// LaunchedEffect(viewModel.navigationEvent) { +// viewModel.navigationEvent.collect { event -> +// when (event) { +// is AuthNavigationEvent.NavigateToRegistrationScreen -> { +// navController.navigate(RegistrationRoute) +// } +// +// else -> {} +// } +// } +// } Scaffold( modifier = Modifier.fillMaxSize() diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt index dd338e4..10c1421 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt @@ -5,13 +5,13 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import ing.bikeshedengineer.debtpirate.PrefsDataStore -import ing.bikeshedengineer.debtpirate.app.host.RegistrationRoute import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepository import ing.bikeshedengineer.debtpirate.auth.domain.usecase.LoginCredentialsValidationResult import ing.bikeshedengineer.debtpirate.auth.domain.usecase.SubmitLoginCredentialsUseCase import ing.bikeshedengineer.debtpirate.auth.domain.usecase.ValidateLoginCredentialsUseCase -import ing.bikeshedengineer.debtpirate.auth.navigation.AuthNavigationEvent import ing.bikeshedengineer.debtpirate.domain.model.Token +import ing.bikeshedengineer.debtpirate.navigation.Destination +import ing.bikeshedengineer.debtpirate.navigation.Navigator import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @@ -37,6 +37,7 @@ enum class InvalidReason { @HiltViewModel class LoginScreenViewModel @Inject constructor( + private val navigator: Navigator, private val authRepository: AuthRepository, private val prefsStore: DataStore, private val submitLoginCredentials: SubmitLoginCredentialsUseCase, @@ -44,9 +45,6 @@ class LoginScreenViewModel @Inject constructor( ) : ViewModel() { // private val storeLoginData = StoreLoginDataUseCase(dataStore) - private val _navigationEvent = MutableStateFlow(null) - val navigationEvent = _navigationEvent.asStateFlow() - private val _emailAddress = MutableStateFlow("") val emailAddress = _emailAddress.asStateFlow() @@ -101,6 +99,8 @@ class LoginScreenViewModel @Inject constructor( } fun onRegisterButtonClick() { - _navigationEvent.value = AuthNavigationEvent.NavigateToRegistrationScreen + viewModelScope.launch { + navigator.navigate(destination = Destination.RegistrationScreen) + } } } \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/NavigatorModule.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/NavigatorModule.kt new file mode 100644 index 0000000..68a18c1 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/NavigatorModule.kt @@ -0,0 +1,19 @@ +package ing.bikeshedengineer.debtpirate.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import ing.bikeshedengineer.debtpirate.navigation.Destination +import ing.bikeshedengineer.debtpirate.navigation.Navigator +import ing.bikeshedengineer.debtpirate.navigation.NavigatorImpl +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object NavigatorModule { + + @Provides + @Singleton + fun provideNavigator(): Navigator = NavigatorImpl(startDestination = Destination.AuthGraph) +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Destination.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Destination.kt new file mode 100644 index 0000000..b6e1a22 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Destination.kt @@ -0,0 +1,16 @@ +package ing.bikeshedengineer.debtpirate.navigation + +import kotlinx.serialization.Serializable + +sealed interface Destination { + + /* Auth Destinations */ + @Serializable + data object AuthGraph : Destination + + @Serializable + data object LoginScreen : Destination + + @Serializable + data object RegistrationScreen : Destination +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/NavigationAction.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/NavigationAction.kt new file mode 100644 index 0000000..5fe2061 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/NavigationAction.kt @@ -0,0 +1,13 @@ +package ing.bikeshedengineer.debtpirate.navigation + +import androidx.navigation.NavOptionsBuilder + +sealed interface NavigationAction { + + data class Navigate( + val destination: Destination, + val navOptions: NavOptionsBuilder.() -> Unit = {} + ) : NavigationAction + + data object NavigateUp : NavigationAction +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Navigator.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Navigator.kt new file mode 100644 index 0000000..ea0ee8c --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/navigation/Navigator.kt @@ -0,0 +1,34 @@ +package ing.bikeshedengineer.debtpirate.navigation + +import androidx.navigation.NavOptionsBuilder +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.receiveAsFlow +import javax.inject.Inject + +interface Navigator { + val startDestination: Destination + val navigationActions: Flow + + suspend fun navigate(destination: Destination, navOptions: NavOptionsBuilder.() -> Unit = {}) + + suspend fun navigateUp() +} + +class NavigatorImpl( + override val startDestination: Destination +) : Navigator { + private val _navigationActions = Channel() + override val navigationActions = _navigationActions.receiveAsFlow() + + override suspend fun navigate( + destination: Destination, + navOptions: NavOptionsBuilder.() -> Unit + ) { + _navigationActions.send(NavigationAction.Navigate(destination, navOptions)) + } + + override suspend fun navigateUp() { + _navigationActions.send(NavigationAction.NavigateUp) + } +} \ No newline at end of file