Communicate with other fragments - why best practice changed - android

Why Android team changed best practice to how to assign an interface to fragment.
Before it was on the fragment onAttach(context: Context) we cast the context to the interface.
private lateinit var onHeadlineSelectedListener: OnHeadlineSelectedListener
override fun onAttach(context: Context?) {
super.onAttach(context)
onHeadlineSelectedListener = activity as OnHeadlineSelectedListener
}
Now Android recommend to do it on onAttachFragment() here's the link below:
class MainActivity : Activity(), HeadlinesFragment.OnHeadlineSelectedListener {
// ...
fun onAttachFragment(fragment: Fragment) {
if (fragment is HeadlinesFragment) {
fragment.setOnHeadlineSelectedListener(this)
}
}
}
https://developer.android.com/training/basics/fragments/communicating#kotlin

It's better now to use viewModel to save the state of the data or the action and you can have live data in between
1.so create the activity and then create SheredViewModel
2.add livedata:LiveData
add your view model to your activity by viewModelProviders.of(this)[SheredViewModel]
add the observer in the activity if you want to listen to the changes in the activity or any where else
go to you'r fragment add the viewModelProviders.of(activity)[SheredViewModel]
now in this case if you post any data to the liveData:LiveData any one can listen to your changes elegant and clean
you can have a look on the android document for this example in this link
https://developer.android.com/topic/libraries/architecture/viewmodel

Related

How to pass a button click from Fragment that is in a BottomSheet Toolbar to the Main Activity?

I have implemented a TabLayout (which uses fragments) in my bottom sheet toolbar that has buttons which should affect the Main Activity. How do I pass the button clicks from the fragments in my TabLayout to the Main Activity?
I'm stuck and I don't know where to start.
There are multiple methods to do communication between fragments and its activity . I'll explain the ones which are used widely.
Using an interface.
Using a SharedViewModel for all your fragments and its activity . ( this can be used if you are implementing MVVM architecture )
check this out link
EDIT :
This is a simple step by step implementation on how to pass data from a fragment to activity . I am just using dummy class names and method parameters .
Create a folder called 'listeners' inside your app module , this is where you should have all your interface classes. ( This is just for a clean approach , if that is not your priority then you can save the interface class anywhere ). for Eg I am making TabLayoutFragmentClickListner.
interface TabLayoutFragmentClickListener {
}
Add a method to this interface . This is the method which would be called when we click a button inside the fragment. add the required parameters which needs to be passed from fragment to the activity. In this case I am just using a String.
interface TabLayoutFragmentClickListener {
fun onLayoutFragmentClick(value : String)
}
Implement this interface in the activity in which you want the data to be received.
class MainActivity : AppCompatActivity() , TabLayoutFragmentClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
This would make you override the interface method inside that activity.
class MainActivity : AppCompatActivity() , TabLayoutFragmentClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onLayoutFragmentClick(value: String) {
Log.d("testing" , value)
}
}
This overiden method is the definition for your interface method in this activity. Hence when you access the interface method from your fragment, the overidden method inside the activity would be called. Try understanding how interface works in java or kotlin.
Now initialise the instance of the listener in your fragment's onAttach method. like this
class TestingFragment : Fragment() {
lateinit var listener: TabLayoutFragmentClickListener
override fun onAttach(context: Context) {
super.onAttach(context)
listener = context as TabLayoutFragmentClickListener
}
}
now call the interface method from your fragment with the required parameter. This would hence trigger the interface method definition in your activity hence passing data from the fragment to the activity.
class TestingFragment : Fragment() {
lateinit var listener: TabLayoutFragmentClickListener
override fun onAttach(context: Context) {
super.onAttach(context)
listener = context as TabLayoutFragmentClickListener
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
test_btn.setOnClickListener {
listener.onLayoutFragmentClick("testing string")
}
}
Hope this helps.
I think You need to create a function in MainActivity And call that function in the fragment. You can easily access that function bcoz it is your parent activity so you can use it on the button.
References:
I know this is in java but I think it's helpful for you link

View binding in traditional way using findViewbyId in LifecyclerObserver

I am new to the lifecycle observer (fragment). I am trying to link the views defined in XML with fragment. traditionally, we use to do it in onActivityCreated method using findViewById. How can we do it while using lifecycle observer?
Kindly do not suggest data binding. I am trying to avoid it in this scenario.
You can do it this way
class TestFragment : Fragment(), LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun doSomethingOnActivityCreated(){
requireActivity().lifecycle.removeObserver(this)
//do stuff
}
override fun onAttach(context: Context) {
super.onAttach(context)
requireActivity().lifecycle.addObserver(this)
}
}

How can we achieve Shared View Model communication between a Fragment and Activity, where the Activity is not the Parent

I am trying to achieve Fragment to Activity communication given that the Activity is not the parent Activity.
So, I have a MainActivity that has a fragment called ContactListFragment, while the add button on the BottomNavigationView of MainActivity is clicked, I am opening another AddContactActivity to add a contact. My requirement is to when I am clicking the save button on the AddContactActivity, I need to initiate a data-sync with server in ContactListFragment. I think the best approach would be to use a Shared View Model for this, but in that Case how should I create the view model so that the lifecycle owner doesn't get changed?
I thought about using the Application Context as the owner but I feel like its an overkill for a task like this, and it may produce some consequences down the line when other modules are added to the project.
So is there a way to efficiently implement this approach? Thanks.
Write an interface class with object/objects/Data types you need to sync
interface OnSaveClickListener {
fun onSaveClicked(contact: Contact)
}
Now in ContactListFragment class
class ContactListFragment : Fragment(), OnSaveClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(activity as AddContactACtivity).mOnSaveClickListener = this
}
override fun onSaveClicked(contact: Contact) {
// Whatever you want to do with the data
}
}
In AddContactActivity,
class AddContactActivity {
var mOnSaveClickListener : OnSaveClickListener? = null
private void whenYouClickSave(contact: Contact){
mOnSaveClickListener?.onSaveClicked(contact)
}

Can an Activity access a Fragment to know if a button has been pressed?

The Objective: I'm trying to make a notepad application. What my app does is, a button is pressed to create a new note. This pops up a fragment in which the user types his note. Within the same fragment, I have another button that signifies when the user is done typing.
Question 1: Is there a way by which pressing the other button in the Fragment could trigger a method in my Activity?
Question 2: Would this cause the app to become too bloated? Should I keep the button within my activity itself?
Thank you for your help.
Question 1: Is there a way by which pressing the other button in the Fragment could trigger a method in my Activity?
Sure, the simplest way to do it is:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = MyFragmentBinding.bind(view) // viewBinding enabled
binding.myButton.setOnClickListener {
(requireActivity() as MyActivity).doSomething() // <--
}
}
However, if this Fragment can be used in different Activity instances, then it should expose a Listener with which it exposes its potential events, and doesn't need to know the actual Activity instance it is talking to.
interface ActionHandler {
fun onMyButtonClicked()
}
lateinit var actionHandler: ActionHandler
override fun onAttach(context: Context) {
super.onAttach(context)
actionHandler = context as ActionHandler
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = MyFragmentBinding.bind(view) // viewBinding enabled
binding.myButton.setOnClickListener {
actionHandler.onMyButtonClicked()
}
}
This way, your Fragment will always have a listener to talk to even after config changes / process death, which seems to not be the case for most other answers here.
Question 2: Would this cause the app to become too bloated? Should I keep the button within my activity itself?
This depends on whether the button actually belongs in the Activity, though it probably doesn't. Most modern apps are written as single-Activity anyway, and unless the view is shared among all screens, it's put inside a Fragment, possibly maybe even using <include tags from a common layout resource.
There is an easy way of doing this as your fragments have access to activity (Kotlin) | getActivity() (Java) and by casting it you can use it.
But this is not the proper way of doing this because it affects the modularity of fragments.
The proper way of doing this:
Your activity wants to listen to Fragments events without any overhead:
In Fragment
class MyFragment : Fragment() {
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is MyFragment.Listener) {
listener = context
} else {
throw ClassCastException(context.toString() + " You need to implement MyFragment.Listener")
}
}
interface Listener {
fun onSomethingHappened()
}
private var listener: MyFragment.Listener? = null
fun aMethodInsideFragmentThatHandlesButtonEvents() {
listener?.onSomethingHappened()
}
}
And in your activity:
class MyActivity : AppCompatActivity(), MyFragment.Listener {
override void onSomethingHappened() {
// do your work here
}
...
}
For triggering a method on click of a button in fragment, there are number of ways to achieve this. Try this.
If (getActivity() instanceof MainActivity){
//Getting instance of your activity
MainActivity instance = ((MainActivity)getActivity());
//Using the instance calling the method in your activity
instance.methodName();
}
Use the above code in your fragment on button click.
Another way is using Interface, calling its abstract methods in fragment and overriding it MainActivity; on button click those methods will be called.
Or you can also try using RxEventBus. You can publish it in the fragment and listen in the MainActivity.
Hope this resolves your issue.
Just make your activity implement View.OnClickListener and on your fragment set your activity as onClickListener of your button.
your fragment:
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
myButton.setOnclickListener((MyActivity)getActivity));
}

onActivityCreated is deprecated, how to properly use LifecycleObserver?

Google deprecate fragment’s onActivityCreated() on Android and recommend to use LifeCycleObserver:
To get a callback specifically when a Fragment activity's
* {#link Activity#onCreate(Bundle)} is called, register a
* {#link androidx.lifecycle.LifecycleObserver} on the Activity's
* {#link Lifecycle} in {#link #onAttach(Context)}, removing it when it receives the
* {#link Lifecycle.State#CREATED} callback.
So I try to make it in recommended way, but only state I can observe in Logcat is just State: INITIALIZED.
private lateinit var lifecycleObserver: LifecycleObserver
override fun onAttach(context: Context) {
super.onAttach(context)
hostActivity = context as HostActivity
lifecycleObserver = object : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate() {
Logger.tag("SOME-TAG")d("State: ${lifecycle.currentState}")
if(lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
Logger.tag("SOME-TAG").d("CREATED")
hostActivity.lifecycle.removeObserver(lifecycleObserver)
}
}
}
hostActivity.lifecycle.addObserver(lifecycleObserver)
}
What is wrong in code above?
UPDATE 1: Looks like I forgot to use hostActivity.lifecycle.currentState and checked fragment's lifecycle instead of Activities lifecycle.
UPDATE 2: Suggested by Google approach not worked for
1 Host activity and 2 fragments when you click back button from one to another, cause onAttach never called, but onActivityCreated called.
As per the changelog here
The onActivityCreated() method is now deprecated. Code touching the
fragment's view should be done in onViewCreated() (which is called
immediately before onActivityCreated()) and other initialization code
should be in onCreate(). To receive a callback specifically when the
activity's onCreate() is complete, a LifeCycleObserver should be
registered on the activity's Lifecycle in onAttach(), and removed once
the onCreate() callback is received.
You can do something like this in your fragment class:
class MyFragment : Fragment(), LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreated() {
// ... Your Logic goes here ...
}
override fun onAttach(context: Context) {
super.onAttach(context)
activity?.lifecycle?.addObserver(this)
}
override fun onDetach() {
activity?.lifecycle?.removeObserver(this)
super.onDetach()
}
}
All I needed was onActivityCreated(...), hence I did implement an observer that:
Automatically removes itself (using .removeObserver(...)).
Then calls passed callback (update()).
I did it in next way:
class MyActivityObserver(
private val update: () -> Unit
) : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
owner.lifecycle.removeObserver(this)
update()
}
}
and use it in fragments onAttach (or another lifecycle method) like:
myActivity.lifecycle.addObserver(MyActivityObserver {
myOnActivityCreated()
})
You can consider the Lifecycle.State as the nodes in a graph and Lifecycle.Event as the edges between these nodes.
So you will never reached the State.Created on your ON_CREATE function.
Solution
class YourFragment : Fragment(), LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onCreated(){
Log.i("tag","reached the State.Created")
}
override fun onAttach(context: Context) {
super.onAttach(context)
lifecycle.addObserver(this)
}
override fun onDetach() {
super.onDetach()
lifecycle.removeObserver(this)
}
}
For more details
https://developer.android.com/topic/libraries/architecture/lifecycle#lc
The best way to solve the issue is to use lifecycleScope which is present in the activity lifecycle. Below is the code snippet
override fun onAttach(context: Context) {
super.onAttach(context)
activity?.lifecycleScope?.launchWhenCreated {
setupActionbar()
}
}
How does it work? launchWhenXxx runs the launch block when it automatically reaches the specified state(in this case it is Created) and if the lifecycle goes to the destroyed state it cancels the launched coroutine automatically. Internally lifecycleScope uses Dispatchers.Main.immediate and hence there is no penalty of thread switching
Pros of this approach are following:
You don't have to manually maintain registering and deregistering of the observer
No need to overwrite two lifecycle methods
You have to latest activity and fragment dependencies to use lifecycleScope attached to the lifecycle
onActivityCreated is deprecated in API level 28.
use onViewCreated for code touching the view created by
onCreateView and onCreate for other initialization. To get a
callback specifically when a Fragment activity's onCreate is called,
register a androidx.lifecycle.LifecycleObserver on the Activity's
Lifecycle in onAttach, removing it when it receives the CREATED
callback.
The annotation #OnLifecycleEvent is deprecated too.
This annotation required the usage of code generation or reflection,
which should be avoided. Use DefaultLifecycleObserver or
LifecycleEventObserver instead.
So, to fix the issue with the deprecated onActivityCreated and OnLifecycleEvent annotation you should do the following:
Implement DefaultLifecycleObserver.
Register your class as observer in onAttach().
Override onCreate(owner: LifecycleOwner) and move your code from onActivityCreated in it.
De-register the observer when the CREATE event is received in onCreate()
See Kotlin and Java examples below:
Kotlin:
class YourFragment : Fragment(), DefaultLifecycleObserver {
override fun onAttach(context: Context) {
super.onAttach(context)
// Register your class as observer
activity?.lifecycle?.addObserver(this)
}
override fun onCreate(owner: LifecycleOwner) {
super<DefaultLifecycleObserver>.onCreate(owner)
// Remove the observer
activity?.lifecycle?.removeObserver(this)
//Move here your code from onActivityCreated(savedInstanceState: Bundle?)
}
}
Java:
public class YourFragment extends Fragment implements DefaultLifecycleObserver {
public void onAttach(#NonNull Context context) {
super.onAttach(context);
// Register your class as observer
if (getActivity() != null) {
getActivity().getLifecycle().addObserver(this);
}
}
#Override
public void onCreate(#NonNull LifecycleOwner owner) {
DefaultLifecycleObserver.super.onCreate(owner);
// Remove the observer
if (getActivity() != null) {
getActivity().getLifecycle().removeObserver(this);
}
//Move here your code from onActivityCreated(savedInstanceState: Bundle?)
}
IMPORTANT: Note that onActivityCreated is called after onCreateView, but DefaultLifecycleObserver.onCreate is called before onCreateView. So, if until now you were using in onActivityCreated something initialised in onCreateView, you'll have to move it somewhere else. E.g. in onViewCreated().

Categories

Resources