Skip to content

Instantly share code, notes, and snippets.

@Shipaaaa
Last active March 10, 2024 04:53
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Shipaaaa/667fd22ec234741d374a3b6740c0f193 to your computer and use it in GitHub Desktop.
Save Shipaaaa/667fd22ec234741d374a3b6740c0f193 to your computer and use it in GitHub Desktop.
MVI navigation sample
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)
}
}
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
)
}
}
}
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())
}
}
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