feature TI-195: [Android] Cicirone Navigation
This commit is contained in:
parent
c4b740f629
commit
d71fda5853
|
|
@ -88,6 +88,9 @@ dependencies {
|
|||
// Groupie
|
||||
implementation(libs.groupie)
|
||||
implementation(libs.groupie.viewbinding)
|
||||
|
||||
// Cicecrone
|
||||
implementation(libs.cicerone)
|
||||
}
|
||||
|
||||
apply(from = "${rootProject.ext["buildScriptsDir"]}/gradle/scripts/stringGenerator.gradle")
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
package ru.touchin.template
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class SingleViewModel @Inject constructor() : ViewModel()
|
||||
|
|
@ -2,14 +2,22 @@ package ru.touchin.template.base.fragment
|
|||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import ru.touchin.template.base.viewmodel.BaseController
|
||||
import ru.touchin.template.di.SharedComponent
|
||||
import ru.touchin.template.di.getSharedModule
|
||||
import ru.touchin.template.navigation.backpress.OnBackPressedListener
|
||||
|
||||
abstract class BaseFragment<T : ViewModel> : Fragment() {
|
||||
abstract class BaseFragment<T : ViewModel> : Fragment(), OnBackPressedListener {
|
||||
|
||||
protected val viewModel: T by lazy { createViewModelLazy().value }
|
||||
|
||||
protected val viewModelFactory by lazy { getSharedComponent().viewModelFactory() }
|
||||
|
||||
protected abstract fun createViewModelLazy(): Lazy<T>
|
||||
|
||||
protected fun getSharedComponent(): SharedComponent = getSharedModule()
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
return (viewModel as BaseController).onBackClicked()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package ru.touchin.template.base.viewmodel
|
||||
|
||||
interface BaseController {
|
||||
|
||||
fun onBackClicked(): Boolean = true
|
||||
}
|
||||
|
|
@ -6,12 +6,15 @@ import dagger.Component
|
|||
import javax.inject.Singleton
|
||||
import ru.touchin.template.App
|
||||
import ru.touchin.template.di.modules.AppModule
|
||||
import ru.touchin.template.di.modules.NavigationModule
|
||||
import ru.touchin.template.di.modules.ViewModelModule
|
||||
import ru.touchin.template.feature.SingleActivity
|
||||
|
||||
@Component(
|
||||
modules = [
|
||||
AppModule::class,
|
||||
ViewModelModule::class
|
||||
ViewModelModule::class,
|
||||
NavigationModule::class
|
||||
]
|
||||
)
|
||||
@Singleton
|
||||
|
|
@ -27,4 +30,6 @@ interface AppComponent : SharedComponent {
|
|||
}
|
||||
|
||||
fun inject(entry: App)
|
||||
|
||||
fun inject(entry: SingleActivity)
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package ru.touchin.template.di.modules
|
||||
|
||||
import com.github.terrakok.cicerone.Cicerone
|
||||
import com.github.terrakok.cicerone.NavigatorHolder
|
||||
import com.github.terrakok.cicerone.Router
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
class NavigationModule {
|
||||
|
||||
private val cicerone: Cicerone<Router> = Cicerone.create()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRouter(): Router {
|
||||
return cicerone.router
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNavigatorHolder(): NavigatorHolder {
|
||||
return cicerone.getNavigatorHolder()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package ru.touchin.template.feature
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import com.github.terrakok.cicerone.NavigatorHolder
|
||||
import com.github.terrakok.cicerone.androidx.AppNavigator
|
||||
import javax.inject.Inject
|
||||
import ru.touchin.template.R
|
||||
import ru.touchin.template.base.activity.BaseActivity
|
||||
import ru.touchin.template.databinding.ActivityMainBinding
|
||||
import ru.touchin.template.di.DI
|
||||
|
||||
class SingleActivity : BaseActivity<SingleViewModel>() {
|
||||
|
||||
@Inject
|
||||
lateinit var navigatorHolder: NavigatorHolder
|
||||
|
||||
private val rootNavigator by lazy {
|
||||
AppNavigator(this, R.id.activityScreensContainer)
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
|
||||
override fun createViewModelLazy(): Lazy<SingleViewModel> = viewModels { viewModelFactory }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
DI.getComponent().inject(this)
|
||||
|
||||
viewModel.navigate()
|
||||
|
||||
}
|
||||
|
||||
override fun onResumeFragments() {
|
||||
super.onResumeFragments()
|
||||
navigatorHolder.setNavigator(rootNavigator)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
navigatorHolder.removeNavigator()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package ru.touchin.template.feature
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.github.terrakok.cicerone.Router
|
||||
import javax.inject.Inject
|
||||
import ru.touchin.template.navigation.Screens
|
||||
|
||||
class SingleViewModel @Inject constructor(
|
||||
private val router: Router,
|
||||
) : ViewModel() {
|
||||
fun navigate() {
|
||||
router.newRootScreen(Screens.First())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package ru.touchin.template.feature.first
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.viewModels
|
||||
import ru.touchin.template.base.fragment.BaseFragment
|
||||
import ru.touchin.template.databinding.FragmentFirstBinding
|
||||
|
||||
class FirstFragment : BaseFragment<FirstViewModel>() {
|
||||
|
||||
private var _binding: FragmentFirstBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun createViewModelLazy() = viewModels<FirstViewModel> { viewModelFactory }
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = FragmentFirstBinding.inflate(layoutInflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
with(binding) {
|
||||
button.apply {
|
||||
text = "Next"
|
||||
setOnClickListener { viewModel.onNextButtonClicked(this@FirstFragment::class.toString()) }
|
||||
}
|
||||
|
||||
textView.text = "First Fragment"
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package ru.touchin.template.feature.first
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.github.terrakok.cicerone.Router
|
||||
import javax.inject.Inject
|
||||
import ru.touchin.template.navigation.Screens
|
||||
|
||||
class FirstViewModel @Inject constructor(
|
||||
private val router: Router
|
||||
) : ViewModel() {
|
||||
|
||||
fun onNextButtonClicked(from: String) {
|
||||
router.navigateTo(Screens.Second(from))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package ru.touchin.template.feature.second
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.touchin.template.base.fragment.BaseFragment
|
||||
import ru.touchin.template.databinding.FragmentSecondBinding
|
||||
import ru.touchin.template.di.viewmodel.assistedViewModel
|
||||
|
||||
class SecondFragment : BaseFragment<SecondViewModel>() {
|
||||
|
||||
companion object {
|
||||
private const val FROM_KEY = "FROM"
|
||||
private const val SCREEN_NAME_KEY = "SCREEN_NAME"
|
||||
private const val FRAGMENT_NAME = "Second Fragment"
|
||||
|
||||
fun newInstance(from: String): SecondFragment {
|
||||
val args = bundleOf(FROM_KEY to from, SCREEN_NAME_KEY to FRAGMENT_NAME)
|
||||
|
||||
val fragment = SecondFragment().apply {
|
||||
arguments = args
|
||||
}
|
||||
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
private var _binding: FragmentSecondBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val secondViewModelFactory by lazy { getSharedComponent().secondScreenViewModelFactory() }
|
||||
|
||||
override fun createViewModelLazy() = assistedViewModel {
|
||||
secondViewModelFactory.create(
|
||||
arguments?.getString(FROM_KEY) ?: "Unkown Fragment",
|
||||
arguments?.getString(SCREEN_NAME_KEY) ?: "Unknown"
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = FragmentSecondBinding.inflate(layoutInflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
with(binding) {
|
||||
button.apply {
|
||||
text = "Back"
|
||||
setOnClickListener { onBackPressed() }
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.state
|
||||
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
|
||||
.collect {
|
||||
textView.text = it
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package ru.touchin.template.feature.second
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.github.terrakok.cicerone.Router
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import ru.touchin.template.base.viewmodel.BaseController
|
||||
|
||||
class SecondViewModel @AssistedInject constructor(
|
||||
@Assisted("from") from: String,
|
||||
@Assisted("screenName") screenName: String,
|
||||
private val rootRouter: Router
|
||||
) : ViewModel(), BaseController {
|
||||
|
||||
private val _state = MutableStateFlow("$from to $screenName")
|
||||
val state: StateFlow<String> = _state.asStateFlow()
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
@Assisted("from") from: String,
|
||||
@Assisted("screenName") screenName: String
|
||||
): SecondViewModel
|
||||
}
|
||||
|
||||
override fun onBackClicked(): Boolean {
|
||||
rootRouter.exit()
|
||||
return super.onBackClicked()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package ru.touchin.template.navigation
|
||||
|
||||
import com.github.terrakok.cicerone.androidx.FragmentScreen
|
||||
import ru.touchin.template.feature.first.FirstFragment
|
||||
import ru.touchin.template.feature.second.SecondFragment
|
||||
|
||||
object Screens {
|
||||
|
||||
fun First(): FragmentScreen = FragmentScreen {
|
||||
FirstFragment()
|
||||
}
|
||||
|
||||
fun Second(from: String): FragmentScreen = FragmentScreen {
|
||||
SecondFragment.newInstance(from)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package ru.touchin.template.navigation.backpress
|
||||
|
||||
interface OnBackPressedListener {
|
||||
|
||||
fun onBackPressed(): Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.template.navigation.router
|
||||
|
||||
import com.github.terrakok.cicerone.Router
|
||||
|
||||
interface RouterProvider {
|
||||
val router: Router
|
||||
}
|
||||
Loading…
Reference in New Issue