I think I got navigation working?

This commit is contained in:
Z. Charles Dziura 2024-10-31 23:48:14 -04:00
parent db7264c3b9
commit dd0cc9ba2f
10 changed files with 153 additions and 45 deletions

View file

@ -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<LoginRoute>() { LoginScreen(navController = navController) }
composable<RegistrationRoute>() { RegistrationScreen() }
NavHost(navController = navController, startDestination = Destination.AuthGraph) {
navigation<Destination.AuthGraph>(startDestination = Destination.LoginScreen) {
composable<Destination.LoginScreen>() { LoginScreen(navController = navController) }
composable<Destination.RegistrationScreen>() { RegistrationScreen() }
}
}
}
}
}
}
@Composable
private fun <T> ObserveAsEvents(flow: Flow<T>, onEvent: (T) -> Unit) {
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(lifecycleOwner.lifecycle) {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
withContext(Dispatchers.Main.immediate) {
flow.collect(onEvent)
}
}
}
}

View file

@ -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()
}

View file

@ -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

View file

@ -1,6 +0,0 @@
package ing.bikeshedengineer.debtpirate.auth.navigation
sealed class AuthNavigationEvent {
object NavigateToRegistrationScreen : AuthNavigationEvent()
object NavigateToLoginScreen : AuthNavigationEvent()
}

View file

@ -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<LoginScreenViewModel>()
) {
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()

View file

@ -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<PrefsDataStore>,
private val submitLoginCredentials: SubmitLoginCredentialsUseCase,
@ -44,9 +45,6 @@ class LoginScreenViewModel @Inject constructor(
) : ViewModel() {
// private val storeLoginData = StoreLoginDataUseCase(dataStore)
private val _navigationEvent = MutableStateFlow<AuthNavigationEvent?>(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)
}
}
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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<NavigationAction>
suspend fun navigate(destination: Destination, navOptions: NavOptionsBuilder.() -> Unit = {})
suspend fun navigateUp()
}
class NavigatorImpl(
override val startDestination: Destination
) : Navigator {
private val _navigationActions = Channel<NavigationAction>()
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)
}
}