Passing Data between fragments - android

I've looked at various examples and dont quite understand exactly how this can be done.
I have 2 fragments each hosted by a seperate Activity and need to pass data between the two and update each other based on choices.
I have a clickable Textview in fragment1 that should open fragment2 and allow the user to select a choice from another list of TextViews each displaying a different option.
I want the choice of the user in fragment2 to update the text of the TextView in fragment1 based on the choice that was made.
I have a listener to start fragment 2 when the TextView is clicked:
v3.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent i = new Intent(getActivity(), FragmentActivity2.class);
getActivity().startActivity(i);
}
});
When I click a TextView in fragment2 I would like the fragment to close, return to fragment1 and update the text

The easiest way to pass data between fragments is using listeners. You can implement one/two interface to exchange data (one for each activity). Your activity should implement an interface and then call the methdod in the inteface to update data:
public interface ChangeLinkListener {
public void onLinkChange(String link);
}
In the onclick method:
(ChangeLinkListener) getActivity()).onLinkChange(data.getLink());
Where onLinkChange is simply a method for example you could change the name as you prefer.
I've written a tutorial between passing data if you like you can look here

Related

Fragment not attached to host

Let me explain the whole thing, just in case. I'm using BottomNavigationView with Jetpack's Mobile Navigation graphs and so. I reimplemented the method:
NavigationUI.setupWithNavController(...)
during navigation setup, instead of using this method I made a very similar:
customSetupWithNavController(...)
the only changes I made was to change de default fading transition between fragments, and started to using more natural (in my opinion) side slide animations, just like this in onNavDestinationSelected:
if(origin == 0){
builder.setEnterAnim(R.anim.slide_in_from_right)
.setExitAnim(R.anim.slide_out_to_left)
.setPopEnterAnim(R.anim.slide_in_from_left)
.setPopExitAnim(R.anim.slide_out_to_right);
}else if(origin == 1){
builder.setEnterAnim(R.anim.slide_in_from_left)
.setExitAnim(R.anim.slide_out_to_right)
.setPopEnterAnim(R.anim.slide_in_from_right)
.setPopExitAnim(R.anim.slide_out_to_left);
}
Where origin stands for the direction where the incoming Fragment comes from.
It came with a problem: 2 of my fragments need a FAB to add elements to a recycler, and the side slide transitioning suddenly became ugly. So I added a single FAB in MainActivity's Layout, and a logic shows the FAB only when these 2 fragments are called.
I couldn't find a nice way to pass the click event from Activity to Fragments, because I wasn't able to instantiate the Fragments, since the Navigation handles the whole process.
So, what I did was to create a ViewHolder, since I know it can survive trough lifecycle changes. This ViewHolder holds an int, and a MutableLiveData, in the MainActivity logic I pass the current selected id of the element selected by the BottomNavigationView to the int, and only if the MainActivity's FAB is clicked the live Boolean is set to true. So, in Fragments onViewCreated() I added and observer to this Boolean, when the value is set to true, and the id passed to the ViewHolder matches with the id of the current fragment, the Boolean is set back to false, and something can be done, it's something like this:
eventsNotificationHandler.getClickEvent().observe(requireActivity(), new Observer<Boolean>() {
#Override
public void onChanged(Boolean aBoolean) {
if(aBoolean && eventsNotificationHandler.getPositionId() == R.id.nav_contacts){
eventsNotificationHandler.setClickEvent(false);
//do something here
}
}
});
This notificationHandler is the ViewHolder.
So far so good, at this point I can:
1- Navigate between BottomNavigationView' Fragments freely, and the FAB shows only for needed fragments.
2- Use Log.d(...) inside the observer any time I want, and see that the debug message just fine.
3- Toast a message, any time I want, ONLY if the context parameter was initialized outside the Observer, something like this:
Toast.makeText(previouslyDefinedContext, "SOME TEXT", Toast.LENGTH_SHORT).show();
What I can't:
1- Launch an Activity whenever I want, from inside the observer by using same idea than before, ONLY initializing the context before, and outside the Observer I was able to start the intent, just like this:
eventsNotificationHandler.getClickEvent().observe(requireActivity(), new Observer<Boolean>() {
#Override
public void onChanged(Boolean aBoolean) {
if(aBoolean && eventsNotificationHandler.getPositionId() == R.id.nav_contacts){
eventsNotificationHandler.setClickEvent(false);
Intent newContact = new Intent(previouslyDefinedContext, NewContactActivity.class);
startActivity(newContact);
requireActivity().overridePendingTransition(R.anim.slide_in_from_right,R.anim.slide_out_to_left);
}
}
});
But in this particular case I can launch the new Activity as many times as I want, BUT ONLY if I navigate directly to this particular fragment where the observer is defined after the app opens, if I decide to navigate first trough some other fragments instead, and then I go to this fragment to try to launch the Activity, the app crashes
I've noticed that this exact behavior happens when I call requireContext() from inside the Observer, it works but then stops working.
The app crashes with:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: cu.arrowtech.bpawreckproject, PID: 18019
java.lang.IllegalStateException: Fragment FragmentContacts{883259b} (9f127bdb-127d-4366-b90b-c8900a5a771e)} not attached to Activity
What I want:
1- A right way to launch a new Activity from inside a Fragment, by pressing a FAB in MainActivity, if it's possible.
2- A nice way to switch fragments if a possible solution implies to change the logic I have already.
3- Keep using Jetpack and Navigation Graphs.
I'm able to do what I want by using 2 separate FABs in each Fragment, but the transitioning is not nice and beautiful.
I'm open to suggestions, even if that implies to change the logic. I'm almost certain it must be a better way to do what I'm doing, instead of using ViewHolder for this purpose.
I would like to get something similar to the Google Pay, it seems to be that the Buttons for adding payment method, passes, and transfers is the same button, but it adapts to each situation.
After some reasearch I did found a way to keep fluid transitions between fragments AND Mobile Navigation components AND a single FAB in the MainActivity layout.
What I did was to use an Interface instead of ViewModels (I always knew that approach was wrong):
public interface SharedViewsInterface {
void onFabClicked();
}
So in my MainActivity I defined that interface as null, and created a method to define it according to the situation. My MainActivity looks like:
public class MainActivity extends AppCompatActivity {
//UI elements
private FloatingActionButton main_fab;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Same dode to get the custom animations
//.......
//Main and only Fab configuration
main_fab = findViewById(R.id.main_fab);
main_fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(sharedViewsInterface !=null){
sharedViewsInterface.onFabClicked();
}
}
});
}
//Other auxiliary methods
public void setInterface(SharedViewsInterface sharedViewsInterface){
this.sharedViewsInterface = sharedViewsInterface;
}
}
By doing this, I can, from each fragment to implement the Interface in onCreate by doing this:
((MainActivity) requireActivity()).setInterface(new SharedViewsInterface() {
#Override
public void onFabClicked() {
Intent newContact = new Intent(requireContext(), NewContactActivity.class);
startActivity(newContact);
requireActivity().overridePendingTransition(R.anim.slide_in_from_right_short,R.anim.slide_out_to_left_medium);
}
});
This works well becouse the FAB is shown only when a fragment with Interface implementation is visible, see my example gif

Sharing data between two fragments

I'm currently trying to make a create account fragment and a choose profile pic fragment.
Fragment A/create account:
This houses Edittextfields for name, password, E-mail address.
There is also an Image view which represents the profile pic.
This Image view has a button on the side, which I want to use to switch over to Fragment B.
Fragment B/Choose Profile Pic:
Fragment is just an Image view and a couple of Image Buttons.
My Problem
Let's say the user has typed in all information on Fragment A and now wants to choose a profile pic, how do I save all the data temporary which is entered in Fragment A.
The second question is then, when the user clicks the Accept button on Fragment B, how do I sent the chosen Pic to Fragment A. So that I can switch over to Fragment A and restore the previously saved information and receive the drawable Image which the user has selected, to display it in the Image view.
Have a look at the new Android Architecture Components. Especially ViewModel.
https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing
There is a section on sharing data between fragments.
It's very common that two or more fragments in an activity need to
communicate with each other. Imagine a common case of master-detail
fragments, where you have a fragment in which the user selects an item
from a list and another fragment that displays the contents of the
selected item. This case is never trivial as both fragments need to
define some interface description, and the owner activity must bind
the two together. In addition, both fragments must handle the scenario
where the other fragment is not yet created or visible.
This common pain point can be addressed by using ViewModel objects.
These fragments can share a ViewModel using their activity scope to
handle this communication, as illustrated by the following sample
code:
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}
Making Activity as an intermediate can enable your fragment communication.
Refer Communicating with Other Fragments for "fragment-activity-fragment" communication. If you want to persist the data refer SharedPreference of android.
Why you want to create 2 Fragments ?
You can achieve same thing with different views. Let's say you can create one Linear/Relative(Layout Group) Layout with all the controls of 1st Fragment and create another layout(Layout Group) with 2nd Fragment controls. And then using animation show and hide layouts so you can have both the things in same fragment or activity and user won't even notice that thing.
This way you don't have to save data temporary and you will have to implement all the things in single file.

How to pass data from activity to Fragment which is inside viewpager

I have one tab activity that contain 3 tabs.
I have one edittext and button in my activity, one textview in fragments.
Whenever I need to change the fragment textview I simply add some text in edittext and click the button after. That text should appear in the fragment.
Here I am not able to use setArguments.
If you are using ViewPager to render the fragment use this code in your parent Activity.
if(viewPager.getCurrentItem() == 1) //First fragment
{
FragmentOne frag1 = (FragmentOne)viewPager.getAdapter().instantiateItem(viewPager, viewPager.getCurrentItem());
frag1.textview.setText(yourText);
}
Otto's EventBus to the rescue.
I had exactly this situation to handle. In my case I needed to trigger a fragment change inside a viewpager with locked swipes. So
Gradle:
// EventBus
implementation 'org.greenrobot:eventbus:3.0.0'
Make an event which will be passed from the activity to the fragment
public class FragmentChangeEvent {
public int fragmentToBeChanged;
//here you can define variables of your choosing, just make sure you're including them into the constructor of the event.
public FragmentChangeEvent(int fragmentToBeChanged) {
this.fragmentToBeChanged = fragmentToBeChanged;
}
}
Trigger the event from the activity
EventBus.getDefault().post(new FragmentChangeEvent(1));
And finally make your fragment aware of the bus and the events
#Subscribe(threadMode = ThreadMode.MAIN)
public void onChangeFragmentEvent(FragmentChangeEvent event) {
//do your work here.
}
}

How to start a fragment from a RecyclerView Adapter which is inside another fragment?

I have a tab view with two fragments. Those two fragments contain a recycler view with cards.
Each card in both fragments had a button.
Clicking on fragment 1's button should open the fragment 2 as a separate page and vice-versa.
I am struggling to find a method to implement this without making every too complex and tightly coupled.
This is fragment one with its own Adapter.
And this is fragment two:
Clicking on that SELECT DONOR button in Donees page should open donor fragment in a new page where the user will be able to assign a donor for the selected donee.
So I have two needs here
1) To start a fragment from a fragment
2) To Keep track from which Donee the new donor page was opened so that I can assign a donor for that specific donee.
I hope this is understandable.
so far I have tried LocalBroadcast and FragmentManager but its hard to keep track of what I'm doing with the code.
Can you guys suggest a better technique to achieve this ?
the easiest solution would probably be, starting a new activity, passing something like an ID, name or something to the intent on an Button click.
Context.startActivity(new Intent(Context, YourAssigneeActivity.class)
.putExtra("ID",id));
So I assume that you do not switch to the other tab when you click a button on one tab. Therefore the fragment should fill the whole screen.
With this assumption in mind you most likely have to switch the Activity. This can be dones easily with an Intent:
Intent intent = new Intent(getActivity(), ActivityB.class)
intent.putExtra("KEY", <your required data to transfer>);
getActivity().startActivityForResult(intent);
Note that when you use putExtra() don't forget that you need to implement Parcelable in those objects (explained here)
To get to know which item was clicked you can use the following pattern (pseudocode - I personally think it's really clean):
FragmentA implements YourAdapter.callback {
onItemClicked(<YourObject> item) {
<starting new activity as described above>
}
}
class YourAdapter extends RecyclerView.Adapter {
Callback mCallback;
YourAdapter(Context context, otherStuff) {
mCallback = (Callback) context;
}
interface Callback {
onItemClicked(<YourObject> item)
}
YourViewHolder implements View.OnClickListener {
onClick(View v) {
mCallback.onItemClicked(<YourObject> item)
}
}
}
Once you are in your Activity, you can set the Fragment in onCreate() of your Activity. In the Activity retrieve the data with getIntent() in the onCreate before creating the Fragment. Then you can put your data in the Fragment with setArguments(<Bundle>). In the Fragment in the onCreateview() retrieve it with getArguments().
I know this is kind of conmplicated. A better solution would be to just switch to an Activity and forget about the Fragment. This would remove one layer of complexity.
If you directly go from Fragment to Fragment you can ignore the Activity part but the rest should stay the same.
Hope this was the answer you were looking for!
Edit: Note that mCallback = (Callback) context is null if the Activity is not implementing Callback

Passing ListView to another Listview

I have listview in one fragment that displays item from a sqlite database. When I click on one item it opens a menu to allow to open, add to another listview or delete. I want to be able to add it to another listview in another fragment on press. I tried adding it to another listview using bundles but I had no luck. I have a plan of creating another database table to store the added data and display it in a new listview. Would this work? or do any of you guys have another suggestion?
You can send data from one fragment to another. The best way to do this is by going through the parent activity. Something like this:
public class MyActivity extends FragmentActivity implements MyFragmentListener {
MyFragment1 frag1;
MyFragment2 frag2;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(0);
FragmentManager fm = getSupportFragmentManager();
frag1 = (MyFragment1) fm.findFragmentByTag("frag1");
frag1.setListener(this);
frag2 = (MyFragment2) fm.findFragmentByTag("frag2");
}
// Call this function from frag1 when you want to send the data to frag2
public void addToFrag2(ListItem item) {
frag2.addToList(item);
}
}
// Define whatever methods the fragments want to use to pass data back and forth
public interface MyFragmentListener {
void addToFrag2(ListItem item);
}

Categories

Resources