Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Communication between Fragment and Activity #108

Open
onmyway133 opened this issue Nov 21, 2017 · 1 comment
Open

Communication between Fragment and Activity #108

onmyway133 opened this issue Nov 21, 2017 · 1 comment

Comments

@onmyway133
Copy link
Owner

onmyway133 commented Nov 21, 2017

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

@kimar
Copy link

kimar commented Nov 21, 2017

I don't think there's a silver bullet here but the actual implementation highly aligns to your app's overall architecture.

I personally would try to avoid implicitly dispatching events like done 1 to 3 as there's a risk someone might subscribe to those events who's actually not concerned of consuming them.

If you had an MVP architecture there would probably be a presenter coordinating this. With an MVVM architecture maybe you've got an observable you can observe. That said, I think most of these solutions are find, I would just avoid any that expose those events outside of it's own concern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants