Issue #108
There’s always need for communication, right π Suppose we have OnboardingActivity
that has several OnboardingFragment
. Each Fragment
has a startButton
telling that the onboarding flow has finished, and only the last Fragment
shows this button.
Here are several ways you can do that
1. EventBus π
Nearly all articles I found propose this https://github.com/greenrobot/EventBus, but I personally don’t like this idea because components are loosely coupled, every component and broadcast can listen to event from a singleton, which makes it very hard to reason when the project scales
data class OnboardingFinishEvent()
class OnboardingActivity: AppCompatActivity() {
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onOnboardingFinishEvent(event: OnboardingFinishEvent) {
// finish
}
}
class OnboardingFragment: Fragment() {
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
startButton.onClick {
EventBus.getDefault().post(OnboardingFinishEvent())
}
}
}
Read more
2. Otto π
This https://github.com/square/otto was deprecated in favor of RxJava and RxAndroid
3. RxJava π
We can use simple PublishSubject
to create our own RxBus
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject
// Use object so we have a singleton instance
object RxBus {
private val publisher = PublishSubject.create<Any>()
fun publish(event: Any) {
publisher.onNext(event)
}
// Listen should return an Observable and not the publisher
// Using ofType we filter only events that match that class type
fun <T> listen(eventType: Class<T>): Observable<T> = publisher.ofType(eventType)
}
// OnboardingFragment.kt
startButton.onClick {
RxBus.publish(OnboardingFinishEvent())
}
// OnboardingActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
RxBus.listen(OnboardingFinishEvent::class.java).subscribe({
// finish
})
}
4. Interface
This is advised here Communicating with Other Fragments. Basically you define an interface OnboardingFragmentDelegate
that whoever conforms to that, can be informed by the Fragment
of events. This is similar to Delegate
pattern in iOS π
interface OnboardingFragmentDelegate {
fun onboardingFragmentDidClickStartButton(fragment: OnboardingFragment)
}
class OnboardingFragment: Fragment() {
var delegate: OnboardingFragmentDelegate? = null
override fun onAttach(context: Context?) {
super.onAttach(context)
if (context is OnboardingFragmentDelegate) {
delegate = context
}
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
startButton.onClick {
delegate?.onboardingFragmentDidClickStartButton(this)
}
}
}
class OnboardingActivity: AppCompatActivity(), OnboardingFragmentDelegate {
override fun onboardingFragmentDidClickStartButton(fragment: OnboardingFragment) {
onboardingService.hasShown = true
startActivity<LoginActivity>()
}
}
5. ViewModel
We can learn from Share data between fragments to to communication between Fragment and Activity, by using a shared ViewModel
that is scoped to the activity. This is a bit overkill
class OnboardingSharedViewModel: ViewModel() {
val finish = MutableLiveData<Unit>()
}
class OnboardingActivity: AppCompatActivity(), OnboardingFragmentDelegate {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = ViewModelProviders.of(this).get(OnboardingSharedViewModel::class.java)
viewModel.finish.observe(this, Observer {
startActivity<LoginActivity>()
})
}
}
Note that we need to call ViewModelProviders.of(activity)
to get the same ViewModel
with the activity
class OnboardingFragment: Fragment() {
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val viewModel = ViewModelProviders.of(activity).get(OnboardingSharedViewModel::class.java)
startButton.onClick({
viewModel.finish.value = Unit
})
}
}
7. Lambda
Create a lambda in Fragment
, then set it on onAttachFragment
. It does not work for now as there is no OnboardingFragment
in onAttachFragment
π’
class OnboardingFragment: Fragment() {
var didClickStartButton: (() -> Unit)? = null
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
startButton.onClick {
didClickStartButton?.invoke()
}
}
}
class OnboardingActivity: AppCompatActivity() {
override fun onAttachFragment(fragment: Fragment?) {
super.onAttachFragment(fragment)
(fragment as? OnboardingFragment).let {
it?.didClickStartButton = {
// finish
}
}
}
}
8. Listener in Bundle π
Read more