Last active
March 10, 2024 04:53
-
-
Save Shipaaaa/667fd22ec234741d374a3b6740c0f193 to your computer and use it in GitHub Desktop.
MVI navigation sample
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ru.shipa.app.presentation | |
import android.content.Intent | |
import android.os.Bundle | |
import android.view.View | |
import androidx.appcompat.app.AppCompatDelegate | |
import androidx.fragment.app.Fragment | |
import androidx.fragment.app.FragmentManager | |
import androidx.fragment.app.commit | |
import androidx.lifecycle.Lifecycle | |
import androidx.lifecycle.MutableLiveData | |
import androidx.navigation.Navigation | |
import androidx.navigation.findNavController | |
import javax.inject.Inject | |
class AppActivity : BaseActivity() { | |
@Inject | |
lateinit var persistentStorage: PersistentStorage | |
private val viewModel: AppViewModel by injectViewModel { AppViewModel.Parameters() } | |
override fun onCreate(savedInstanceState: Bundle?) { | |
DI.injectAppScope(this) | |
checkNightMode() | |
setTheme(R.style.Theme_App) | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_app) | |
observe(viewModel.startScreen, ::handleStartScreen) | |
observe(viewModel.commands, ::handleCommand) | |
} | |
private fun getNavController() = findNavController(R.id.activity_app_container_screens) | |
private fun handleStartScreen(startScreen: StartScreenViewState) { | |
val navController = Navigation.findNavController(this, R.id.activity_app_container_screens) | |
val mainGraph = navController.navInflater.inflate(R.navigation.root_nav_graph).apply { | |
startDestination = startScreen.resId | |
} | |
navController.setGraph(mainGraph, startScreen.args) | |
} | |
@Suppress("ComplexMethod") | |
private fun handleCommand(command: ViewCommand) { | |
when (command) { | |
is NavigationCommand -> { | |
when (command) { | |
is NavigationCommand.ShowSplashScreen -> showSplashScreen(command) | |
is NavigationCommand.ToDirection -> getNavController().navigateSafe(command.direction) | |
is NavigationCommand.ToRes -> getNavController().navigateSafe(command.resId, command.args) | |
is NavigationCommand.Up -> getNavController().navigateUp() | |
is NavigationCommand.Back -> if (!getNavController().popBackStack()) finish() | |
is NavigationCommand.BackTo -> getNavController().popBackStack( | |
command.destinationId, | |
command.inclusive | |
) | |
} | |
} | |
} | |
} | |
private fun showSplashScreen(command: NavigationCommand.ShowSplashScreen) { | |
supportFragmentManager.commit { | |
add( | |
R.id.activity_app_container_screens, | |
SplashFragment.newInstance(command.invokedByCall), | |
SplashFragment.TAG | |
) | |
addToBackStack(SplashFragment.TAG) | |
} | |
supportFragmentManager.executePendingTransactions() | |
} | |
private fun checkNightMode() { | |
val savedNightModeValue = persistentStorage.getSavedNightMode(AppCompatDelegate.MODE_NIGHT_UNSPECIFIED) | |
val selectedNightMode = NightModeType.fromValue(savedNightModeValue) | |
AppCompatDelegate.setDefaultNightMode(selectedNightMode.value) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ru.shipa.app.presentation.base.fragment | |
import android.os.Bundle | |
import android.view.View | |
import android.view.WindowManager.LayoutParams.* | |
import androidx.annotation.CallSuper | |
import androidx.annotation.ColorRes | |
import androidx.annotation.IdRes | |
import androidx.annotation.LayoutRes | |
import androidx.appcompat.app.ActionBar | |
import androidx.appcompat.app.AppCompatActivity | |
import androidx.appcompat.widget.Toolbar | |
import androidx.core.content.ContextCompat | |
import androidx.core.view.ViewCompat | |
import androidx.fragment.app.Fragment | |
import androidx.navigation.NavController | |
import androidx.navigation.findNavController | |
import androidx.navigation.fragment.findNavController | |
import com.google.android.material.dialog.MaterialAlertDialogBuilder | |
abstract class BaseFragment(@LayoutRes layoutId: Int) : Fragment(layoutId), BaseView { | |
/** | |
* Action Bar activity или `null`, если его нет. | |
* Если нужно получить не nullable, можно использовать [requireActionBar]. | |
* Чтобы установить actionBar, можно использовать [setActionBar] | |
*/ | |
protected val actionBar: ActionBar? | |
get() = (activity as? AppCompatActivity)?.supportActionBar | |
private val baseActivity by lazy { activity as BaseView } | |
override fun showMessage( | |
messageText: String, | |
actionTitleId: Int?, | |
action: ((View) -> Unit)?, | |
containerResId: Int, | |
anchorViewId: Int?, | |
duration: Int | |
) { | |
baseActivity.showMessage(messageText, actionTitleId, action, containerResId, anchorViewId, duration) | |
} | |
override fun showError( | |
messageText: String, | |
actionTitleId: Int?, | |
action: ((View) -> Unit)?, | |
containerResId: Int, | |
anchorViewId: Int?, | |
duration: Int | |
) { | |
baseActivity.showError(messageText, actionTitleId, action, containerResId, anchorViewId, duration) | |
} | |
@CallSuper | |
protected open fun handleCommand(command: ViewCommand) { | |
when (command) { | |
is NavigationCommand -> handleNavigationCommand(command) | |
is ShowSnackbarMessage -> { | |
showMessage( | |
messageText = command.message, | |
containerResId = messagesContainer, | |
anchorViewId = getSnackbarAnchorView() | |
) | |
} | |
is ShowSnackbarError -> { | |
showError( | |
messageText = command.message, | |
containerResId = messagesContainer, | |
anchorViewId = getSnackbarAnchorView() | |
) | |
} | |
is ShowDialogMessage -> { | |
CommonInfoDialogFragment | |
.newInstance(command.title, command.message) | |
.show(parentFragmentManager, CommonInfoDialogFragment.TAG) | |
} | |
is ShowMaterialDialogMessage -> { | |
MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialogTheme) | |
.setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.bg_round_corners)) | |
.setTitle(command.title) | |
.setMessage(command.message) | |
.setPositiveButton(command.successButtonText) { _, _ -> } | |
.show() | |
} | |
} | |
} | |
private fun getNavController(rootGraph: Boolean = false): NavController { | |
return if (rootGraph) { | |
requireActivity().findNavController(R.id.activity_app_container_screens) | |
} else { | |
findNavController() | |
} | |
} | |
private fun handleNavigationCommand(command: NavigationCommand) { | |
when (command) { | |
is NavigationCommand.ToDirection -> getNavController(command.rootGraph).navigateSafe(command.direction) | |
is NavigationCommand.ToRes -> getNavController(command.rootGraph).navigateSafe( | |
command.resId, | |
command.args | |
) | |
is NavigationCommand.Up -> findNavController().navigateUp() | |
is NavigationCommand.Back -> if (!findNavController().popBackStack()) requireActivity().finish() | |
is NavigationCommand.BackTo -> findNavController().popBackStack( | |
command.destinationId, | |
command.inclusive | |
) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ru.shipa.app.presentation.base.viewmodel | |
import android.content.Context | |
import android.os.Bundle | |
import androidx.annotation.IdRes | |
import androidx.lifecycle.ViewModel | |
import androidx.navigation.NavDirections | |
import io.reactivex.disposables.CompositeDisposable | |
abstract class BaseViewModel(override val context: Context) : ViewModel(), SafelySubscribable { | |
val commands = CommandsLiveData<ViewCommand>() | |
override val compositeDisposable = CompositeDisposable() | |
override fun onCleared() { | |
clearDisposables() | |
super.onCleared() | |
LeakDetectorUtils.watch(this) | |
} | |
protected fun showMessage(message: String) { | |
commands.onNext(ShowSnackbarMessage(message)) | |
} | |
protected fun showError(message: String) { | |
commands.onNext(ShowSnackbarError(message)) | |
} | |
protected fun navigateTo(direction: NavDirections, rootGraph: Boolean = false) { | |
commands.onNext(NavigationCommand.ToDirection(direction, rootGraph)) | |
} | |
protected fun navigateTo(@IdRes resId: Int, args: Bundle? = null, rootGraph: Boolean = false) { | |
commands.onNext(NavigationCommand.ToRes(resId, args, rootGraph)) | |
} | |
protected fun navigateUp() { | |
commands.onNext(NavigationCommand.Up()) | |
} | |
protected fun navigateBack() { | |
commands.onNext(NavigationCommand.Back()) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface ViewCommand | |
sealed class NavigationCommand : ViewCommand { | |
data class ShowSplashScreen(val invokedByCall: Boolean) : NavigationCommand() | |
data class ToDirection( | |
val direction: NavDirections, | |
val rootGraph: Boolean = false | |
) : NavigationCommand() | |
data class ToRes( | |
@IdRes val resId: Int, | |
val args: Bundle? = null, | |
val rootGraph: Boolean = false | |
) : NavigationCommand() | |
class Up : NavigationCommand() | |
class Back : NavigationCommand() | |
data class BackTo(val destinationId: Int, val inclusive: Boolean) : NavigationCommand() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment