Skip to content

Commit

Permalink
Improved navigation type safety. (#256)
Browse files Browse the repository at this point in the history
  • Loading branch information
EranBoudjnah committed Sep 12, 2024
1 parent 47c3b9d commit fe705b9
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.mitteloupe.whoami.di

import androidx.fragment.app.Fragment
import com.mitteloupe.whoami.architecture.presentation.navigation.PresentationNavigationEvent
import com.mitteloupe.whoami.architecture.ui.binder.ViewStateBinder
import com.mitteloupe.whoami.architecture.ui.navigation.mapper.NavigationEventToDestinationMapper
import com.mitteloupe.whoami.architecture.ui.view.ViewsProvider
Expand All @@ -17,12 +18,15 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.components.FragmentComponent
import javax.inject.Named

private typealias NavigationMapper = NavigationEventToDestinationMapper<PresentationNavigationEvent>

@Module
@InstallIn(FragmentComponent::class)
object HistoryUiModule {
@Provides
@JvmSuppressWildcards
@Named(HistoryFragment.NAVIGATION_MAPPER_NAME)
fun providesHistoryDestinationToUiMapper(): NavigationEventToDestinationMapper =
fun providesHistoryDestinationToUiMapper(): NavigationMapper =
HistoryNavigationEventToDestinationMapper()

@Provides
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/java/com/mitteloupe/whoami/di/HomeUiModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.mitteloupe.whoami.di
import android.content.Context
import android.content.res.Resources
import com.mitteloupe.whoami.analytics.Analytics
import com.mitteloupe.whoami.architecture.presentation.navigation.PresentationNavigationEvent
import com.mitteloupe.whoami.architecture.ui.navigation.mapper.NavigationEventToDestinationMapper
import com.mitteloupe.whoami.coroutine.CoroutineContextProvider
import com.mitteloupe.whoami.home.presentation.viewmodel.HomeViewModel
import com.mitteloupe.whoami.home.ui.di.HomeDependencies
Expand Down Expand Up @@ -38,6 +40,7 @@ object HomeUiModule {
fun providesConnectionDetailsToUiMapper() = ConnectionDetailsToUiMapper()

@Provides
@Suppress("UNCHECKED_CAST")
fun providesHomeDependencies(
homeViewModel: HomeViewModel,
homeViewStateToUiMapper: HomeViewStateToUiMapper,
Expand All @@ -51,7 +54,7 @@ object HomeUiModule {
homeViewModel,
homeViewStateToUiMapper,
connectionDetailsToUiMapper,
homeNavigationMapper,
homeNavigationMapper as NavigationEventToDestinationMapper<PresentationNavigationEvent>,
homeNotificationMapper,
errorToUiMapper,
coroutineContextProvider,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import com.mitteloupe.whoami.architecture.ui.navigation.exception.UnhandledDesti
import com.mitteloupe.whoami.architecture.ui.navigation.mapper.NavigationEventToDestinationMapper
import com.mitteloupe.whoami.architecture.ui.navigation.model.UiDestination

class HistoryNavigationEventToDestinationMapper : NavigationEventToDestinationMapper {
override fun toUi(navigationEvent: PresentationNavigationEvent): UiDestination =
class HistoryNavigationEventToDestinationMapper :
NavigationEventToDestinationMapper<PresentationNavigationEvent>(
PresentationNavigationEvent::class
) {
override fun mapTypedEvent(navigationEvent: PresentationNavigationEvent): UiDestination =
when (navigationEvent) {
is Back -> backUiDestination()
else -> throw UnhandledDestinationException(navigationEvent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import android.content.Context
import android.content.Intent
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import com.mitteloupe.whoami.analytics.Analytics
import com.mitteloupe.whoami.architecture.presentation.navigation.PresentationNavigationEvent
import com.mitteloupe.whoami.architecture.ui.navigation.exception.UnhandledDestinationException
import com.mitteloupe.whoami.architecture.ui.navigation.mapper.NavigationEventToDestinationMapper
import com.mitteloupe.whoami.architecture.ui.navigation.model.UiDestination
import com.mitteloupe.whoami.home.presentation.navigation.HomePresentationNavigationEvent
Expand All @@ -19,12 +17,11 @@ class HomeNavigationEventToDestinationMapper(
private val activityContext: Context,
private val ossLicensesMenuIntentProvider: Context.(Class<out Any>) -> Intent =
{ javaClass -> Intent(this, javaClass) }
) : NavigationEventToDestinationMapper {
override fun toUi(navigationEvent: PresentationNavigationEvent): UiDestination =
when (navigationEvent) {
is HomePresentationNavigationEvent -> navigationEvent.toUiDestination()
else -> throw UnhandledDestinationException(navigationEvent)
}
) : NavigationEventToDestinationMapper<HomePresentationNavigationEvent>(
HomePresentationNavigationEvent::class
) {
override fun mapTypedEvent(navigationEvent: HomePresentationNavigationEvent): UiDestination =
navigationEvent.toUiDestination()

private fun HomePresentationNavigationEvent.toUiDestination(): UiDestination = when (this) {
is OnSavedDetails -> history(highlightedIpAddress)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ class HomeNavigationEventToDestinationMapperTest {
assertThat(
actualException.message,
matchesPattern(
"^Navigation to PresentationNavigationEvent\\\$MockitoMock\\\$\\w+ was not handled.$"
"^Navigation to PresentationNavigationEvent\\\$MockitoMock\\\$\\w+ " +
"was not handled.$"
)
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
package com.mitteloupe.whoami.architecture.ui.navigation.mapper

import com.mitteloupe.whoami.architecture.presentation.navigation.PresentationNavigationEvent
import com.mitteloupe.whoami.architecture.ui.navigation.exception.UnhandledDestinationException
import com.mitteloupe.whoami.architecture.ui.navigation.model.UiDestination
import kotlin.reflect.KClass

interface NavigationEventToDestinationMapper {
fun toUi(navigationEvent: PresentationNavigationEvent): UiDestination
abstract class NavigationEventToDestinationMapper<in EVENT : PresentationNavigationEvent>(
private val kotlinClass: KClass<EVENT>
) {
fun toUi(navigationEvent: PresentationNavigationEvent): UiDestination = when {
kotlinClass.isInstance(navigationEvent) -> {
@Suppress("UNCHECKED_CAST")
mapTypedEvent(navigationEvent as EVENT)
}

else -> {
mapGenericEvent(navigationEvent) ?: throw UnhandledDestinationException(
navigationEvent
)
}
}

protected abstract fun mapTypedEvent(navigationEvent: EVENT): UiDestination

protected open fun mapGenericEvent(
navigationEvent: PresentationNavigationEvent
): UiDestination? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.mitteloupe.whoami.coroutine.CoroutineContextProvider
abstract class BaseComposeHolder<VIEW_STATE : Any, NOTIFICATION : PresentationNotification>(
private val viewModel: BaseViewModel<VIEW_STATE, NOTIFICATION>,
private val coroutineContextProvider: CoroutineContextProvider,
private val navigationMapper: NavigationEventToDestinationMapper,
private val navigationMapper: NavigationEventToDestinationMapper<PresentationNavigationEvent>,
private val notificationMapper: NotificationToUiMapper
) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import com.mitteloupe.whoami.architecture.ui.navigation.mapper.NavigationEventTo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

private typealias NavigationMapper = NavigationEventToDestinationMapper<PresentationNavigationEvent>

abstract class BaseFragment<VIEW_STATE : Any, NOTIFICATION : PresentationNotification> :
Fragment,
ViewsProvider {
Expand All @@ -29,7 +31,7 @@ abstract class BaseFragment<VIEW_STATE : Any, NOTIFICATION : PresentationNotific

abstract val viewStateBinder: ViewStateBinder<VIEW_STATE, ViewsProvider>

abstract val navigationEventToDestinationMapper: NavigationEventToDestinationMapper
abstract val navigationEventToDestinationMapper: NavigationMapper

open val navController: NavController
get() = findNavController()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.MaterialToolbar
import com.mitteloupe.whoami.analytics.Analytics
import com.mitteloupe.whoami.analytics.event.Click
import com.mitteloupe.whoami.architecture.presentation.navigation.PresentationNavigationEvent
import com.mitteloupe.whoami.architecture.presentation.notification.PresentationNotification
import com.mitteloupe.whoami.architecture.ui.binder.ViewStateBinder
import com.mitteloupe.whoami.architecture.ui.navigation.mapper.NavigationEventToDestinationMapper
Expand All @@ -26,6 +27,8 @@ import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import javax.inject.Named

private typealias NavigationMapper = NavigationEventToDestinationMapper<PresentationNavigationEvent>

@AndroidEntryPoint
class HistoryFragment :
BaseFragment<HistoryViewState, PresentationNotification>(R.layout.fragment_history),
Expand All @@ -37,8 +40,9 @@ class HistoryFragment :
override lateinit var viewModel: HistoryViewModel

@Inject
@JvmSuppressWildcards
@Named(NAVIGATION_MAPPER_NAME)
override lateinit var navigationEventToDestinationMapper: NavigationEventToDestinationMapper
override lateinit var navigationEventToDestinationMapper: NavigationMapper

@Inject
lateinit var recordDeletionToPresentationMapper: HistoryRecordDeletionToPresentationMapper
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.mitteloupe.whoami.home.ui.di

import com.mitteloupe.whoami.analytics.Analytics
import com.mitteloupe.whoami.architecture.presentation.navigation.PresentationNavigationEvent
import com.mitteloupe.whoami.architecture.ui.navigation.mapper.NavigationEventToDestinationMapper
import com.mitteloupe.whoami.architecture.ui.notification.mapper.NotificationToUiMapper
import com.mitteloupe.whoami.architecture.ui.view.BaseComposeHolder
Expand All @@ -12,11 +13,13 @@ import com.mitteloupe.whoami.home.ui.mapper.ConnectionDetailsToUiMapper
import com.mitteloupe.whoami.home.ui.mapper.ErrorToUiMapper
import com.mitteloupe.whoami.home.ui.mapper.HomeViewStateToUiMapper

private typealias NavigationMapper = NavigationEventToDestinationMapper<PresentationNavigationEvent>

data class HomeDependencies(
val homeViewModel: HomeViewModel,
val homeViewStateToUiMapper: HomeViewStateToUiMapper,
val connectionDetailsToUiMapper: ConnectionDetailsToUiMapper,
private val homeNavigationMapper: NavigationEventToDestinationMapper,
private val homeNavigationMapper: NavigationMapper,
private val homeNotificationMapper: NotificationToUiMapper,
val errorToUiMapper: ErrorToUiMapper,
val coroutineContextProvider: CoroutineContextProvider,
Expand Down

0 comments on commit fe705b9

Please sign in to comment.