My app has a bottomNavigationBar with three fragments. My MainActivity contains a CollapsingToolbar with a RadioGroup inside. Now I get the value of the selected RadioButton in the MainActivity, but I need the value to work with it inside my first fragment. How do I do that? Does every fragment contains its own CollapsingToolbar or is the data passed between the activities?
You can use the fragment's setArguments(Bundle) method where the Bundle has key-value pairs that you've set. For example, your fragment object is yourFragment then you have
Bundle bundle = new Bundle();
bundle.putString("paramKey", "paramVal");
yourFragment.setArguments(bundle);
In the fragment's onCreateView(LayoutInflater, ViewGroup, Bundle) you can access the information with the getArguments() method.
String value = getArguments().getString("paramKey"); // value = "paramVal"
// inflate, return
Have a look at the documentation for more information on setting bundle values.
You can use put the values you want to pass in SharedPreferences
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor edit = prefs.edit();
edit.putString("some_key",someValue); //someValue is a var that containns the value that you want to pass
edit.commit();
Then in your fragment, access the value:
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String value = prefs.getString("some_key","default_value");
Another rather less efficient way
Create a Utility class that will hold all your static variables. You will be able to set and get the values of these variables in all instances of that class
This can also be achieved using Java interfaces like this
Have an Interface defined in your Activity class. Capture the interface instance while committing your fragment which will later be used to send data to fragment
.
class ExampleActivity extends Activity {
//Data listener to be implemented by the fragment class
public interface OnDataListerner{
public void sendData(ArrayList<String> data);
}
//DataListener instance to be captured while committing fragment
OnDataListener fragment;
//commit your fragment and type cast it to OnDataListener
private void commit Fragment(){
fragment = new ExampleFragment();
FragmentTransaction transaction =
getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, exampleFragment);
transaction.addToBackStack(null);
transaction.commit();
}
//used to send data through interface methods which will be defined in fragment
public void sendDataToFragment(){
fragment.sendData(Your data to be send);
}
}
Have this interface implemented in your Fragment class and once Acitivity calls any method on this interface it will be called in this Fragment
public class ExampleFragment extends Fragment implements ExampleActivity.OnDataListerner {
//interface callback which is called when Activity call its method.
public void sendData(ArrayList<String> data){
//Here is your data which can be consumed
}
}
Hope this helps.
You can create a method in the fragments visible to the activity class. Then using this method, you can pass the values. And in implementation of this function, you can do required changes such as UI updates in the fragment.
For example -
MainActivity.java
// in member declarations
private MyFragment frag;
private CentralFragment cFrag;
// initialize the fragment
frag = new MyFragment(args);
cFrag = new CentralFragment(otherArgs);
// onRadioButtonClicked -> assuming selected value = v
frag.onChoice(v);
cFrag.onChoice(v);
MyFragment.java
public void onChoice(Type arg) {
// use the value
someTextView.setText(arg);
}
CentralFragment.java
public void onChoice(Type arg) {
// use the value
otherTextView.set(arg);
}
Related
I am building an app with 2 fragments. The 1st fragment has an ImageView and 2 TextViews and the 2nd fragment has a set of buttons and EditTexts. In the 2nd fragment, I have a button called "Save". When this button is clicked, I want to download the image inside my 1st fragment to my device folder (The ImageView, the URI, the bitmap and canvas objects are all in the 1st fragment).
Because fragments can't communicate with one another directly, I was thinking of doing this with an interface. What I did was:
I've declared my interface in the 2nd fragment
Applied the logic of the interface's method in the MainActivity (which is the shared activity between the 2 fragments)
Fed the necessary parameters for the method in the 1st fragment.
Didn't work
But I don't think that this was the correct order so it's no surprise that it didn't work. How do I apply the interface in a way that a button click in the 2nd fragment downloads the image in the 1st fragment?
You could try one of these three options:
1.) Using callbacks to communicate via the activity
As shown in this article, you can define an interface in fragment 2 which is then called when the button is clicked. Your activity (which holds fragment 2) provides an implementation for that interface in which the activity calls a method in fragment 1 for downloading the image. For example:
Fragment 1 providing the download method
public class Fragment1 extends Fragment {
OnButtonClickedListener mCallback;
public void startImageDownload() {
// Download the image
}
// ...
}
Fragment 2 defining and calling the interface
public class Fragment2 extends Fragment {
OnButtonClickedListener mCallback;
// Some kind of init method called by onCreate etc.
private void init() {
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Call the listener if present
if(mCallback != null) {
mCallback.onButtonClicked();
}
}
});
}
public void setOnButtonClickedListener(Activity activity) {
mCallback = activity;
}
// Container Activity must implement this interface
public interface OnButtonClickedListener {
public void onButtonClicked();
}
// ...
}
The Activity reacting on the Button click and calling the download method
public static class MainActivity extends Activity implements Fragment2.OnButtonClickedListener {
Fragment1 mFragment1;
#Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof Fragment2) {
// Register the listener
Fragment2 fragment2 = (Fragment2) fragment;
fragment2.setOnButtonClickedListener(this);
} else if (fragment instanceof Fragment1) {
// Keep a reference to fragment 1 for calling the "download" method
mFragment1 = (Fragment1) fragment;
}
}
#Override
public void onButtonClicked() {
// Handle the button click
mFragment1.startImageDownload();
}
}
This way you avoid linking the fragments together, instead you have the activity beeing a loose "connection" between fragment 1 and fragment 2.
This is just an exmple, i did not had time to test it. But i hope it helps.
2.) Using a local broadcast
I would recommend using the LocalBroadcastManager for sending a broadcast in fragment 1 (that the button was clicked) and receiving it in fragment 2 (downloading the image). Here is a great article about local broadcasts.
3.) Another option is to use ViewModel
The ViewModel was recently introduced by the new Android Jetpack and "is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations." (from ViewModel Overview).
It can also be used to share data between two fragments: Your activity basically holds the ViewModel and the fragments (inside that activity) can get access to it by calling: ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
I think your scenario you could use Observers or some kind of LiveData to react to the button-click via a ViewModel.
Thanks to #Elletlar for helping me improve my answer.
So my issue is basically my MainActivity is initially loaded with a Fragment, which we will call MyFragment.
I am loading JSON, from online and wanting to pass into my MyFragment.
The problem is arising when setContentView is called in the MainActivity, it is calling onCreateView in MyFragment, which contains getArguments.getSerializable("myTag"). The key isn't passed because I haven't loaded the JSON yet.
Can you help me resolve this issue?
Here is my code:
In my MyFragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if(getArguments() != null) {
coll = (HashSet<String>) getArguments().getSerializable("myTag");
}
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_my, container, false);
}
MainActivity (assume I loaded my JSON already):
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadJSON();
passTagsToFragment(); //passes to the fragment
}
public void passTagsToFragment(){
Bundle bundle = new Bundle();
bundle.putSerializable("myTags", tagsSet);
TagsFragment frag = new MyFragment();
frag.setArguments(bundle);
}
EDIT:
Basically, my issue is that I want to load the MainActivity fully, before even starting to load the Fragment. Not sure how to do that.
EDIT 2:
I fixed the problem here is my code: (Changed the variable names)
MainActivity.java
public TagsFragment passInfoToTagsFramgent(){
Bundle bundle = new Bundle();
bundle.putSerializable("tags", tagsList);
TagsFragment frag = new TagsFragment();
frag.setArguments(bundle);
return frag;
}
in OnPostExecute of MainActvity.java:
Fragment tagFragment = passInfoToTagsFramgent();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, tagFragment);
transaction.commit();
You should call your passTagsToFragment() method in post execute method of your asynctask when all json data gets loaded.
protected void onPostExecute(Long result) {
passTagsToFragment();
}
loadJSON is from online source, so I assume it is an AsyncTask.
I usually do this as a lamda expression (kotlin):
loadJSON({passTagsToFragment()})
loadJSON should take a lamda expression as parameter:
loadJSON(private val callback:()->Unit )
and in the AsyncTask::onPostExecute, you should call the callback:
callback.invoke()
or just
callback()
Then you made sure the fragment is opened when the JSON is loaded and passed to fragment.
OK let me try to make it in Java.
In your AsyncTask which loads JSON, you will need an interface e.g.,
public interface JSONLoadCallback {
void loaded();
}
And the its constructor takes the interface as parameter:
class JSONLoader : AsyncTask<....> {
JSONLoader(JSONLoadCallback callback) {
_callback = callback;
}
#Override
public void onPostExecute() {
_callback.loaded();
}
}
And your Activity implements JSONLoadCallback:
#Override
public void loaded() {
passTagsToFragment();
}
And should pass itself to the AsyncTask:
JSONLoader(this).executeOnExecutor();
This way, the loaded() function is fired when JSON load is finished.
You see, Java codes are very verbal, Kotlin almost removed the necessity of Java interface.
As per my understanding, you can call Loadjson() method on fragment as well and use data accordingly but if you have some specific logic you can use asynctask and on json retrieval with progress bar you can set any MyFragment callback and update your fragment accordingly.
Good day,
If I declared an arraylist on the main activity and populated default values in the list and I would like to display it on a fragment in a custom list , how would I access my list in the fragment?
Thank you
users = new ArrayList<Users>();
In the Fragment
// how do I find the arraylist here?
UserAdapter<User> adapter = new UserAdapter<User> (getContext().user);
First, create a method in your activity that returns the ArrayList.
public ArrayList<User> getUsers() {
return users;
}
Then, in your fragment you need to use the method getActivity() to access your activity. From here, you can cast the call to the specific activity to acess its methods. So your call will look something like this:
ArrayList<User> users = ((MainActivity)getActivity()).getUsers();
UserAdapter<User> adapter = new UserAdapter<User>(users);
Edit: I'm going to show another way to do this to avoid coupling the fragment to the activity. In the first example, you're required to use a specific Activity to get the Users ArrayList. This kind of defeats the purpose of a fragment, which is its re-usability. This fragment won't work straight away if you put it in another Activity because it's bound specifically to MainActivity via the casted getActivity() call.
A better way to do this is to create an interface within your fragment and then have whatever Activity it's attached to implement that interface. This will allow you to add the fragment to any activity that implements this interface without changing the code of the fragment.
Our fragment will look like this:
public class ExampleFragment {
//Create the interface that will be used to communicate with the
//Activity. For simplicity, we'll just call it UsersProvider.
//Whichever Activity uses this Fragment will implement this interface.
public interface UsersProvider {
public ArrayList<User> getUsers();
}
//Our UsersProvider reference.
private UsersProvider usersProvider;
//Here is where we'll set the Activity as our UsersProvider.
//We're still calling getActivity(), but we're not casting it to
//any specific Activity, rather we're casting it as the interface.
#Override
public void onAttach(Context context) {
usersProvider = (UsersProvider) getActivity();
}
// onCreate, onCreateView etc... goes here
}
Our Activity will look like this:
public class MainActivity extends Activity implements ExampleFragment.UsersProvider {
//It's assumed that this is set somewhere else in the acitivty.
private ArrayList<User> users;
//onCreate, etc...
//Implement the method from UsersProvider interface
#Override
public ArrayList<User> getUsers() {
return users;
}
}
So now this is set up in a way that your fragment can be used from any Activity without changing the code in your fragment. Just have an activity implement the UsersProvider interface and you can access your Users ArrayList in your fragment by calling
ArrayList<User> users = usersProvider.getUsers();
In the activity:
Bundle bundle = new Bundle();
// Put the list in a bundle
bundle.putParcelableArrayList("users", users);
YourFragmentClass fragment = new YourFragmentClass();
fragment.setArguments(bundle);
In the fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Get the list
List<Users> users = getArguments().getParcelableArrayList("users");
return inflater.inflate(R.layout.fragment, container, false);
}
What I am basicaly trying is to access a variable inside a Fragment and get rid of it in my activity.
It worked to get the variable of the activity in my fragment but not the other way around:
what I did:
// get method of MainActivity
final MainActivity activity = (MainActivity) getActivity();
is it even possible to make this "the other way around"?
(Access variable of Fragment in my Activity)?
You need to implement listeners.
You can read more about here:Communicating with Other Fragments
Here is a code example how to pass data (or null) from Activity to a Fragment:
public class FragmentA extends Fragment implements FragmentCommunicator{
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
((MainActivity)getActivity()).fragmentCommunicator = this;
}
#Override
public void passDataToFragment(String str) {
//str is the string variable you pass from the Activity, it can be null...
}
}}
Next the FragmentCommunicator Class:
public interface FragmentCommunicator{
public void passDataToFragment(String str);}
And the Activity:
public class MainActivity extends FragmentActivity{
public FragmentCommunicator fragmentCommunicator;
public void someMethod(String someString) {
fragmentCommunicator.passDataToFragment(someString);
}}
When you call passDataToFragment() from the Activity it will pass the string (or any other variable) to the fragment method passDataToFragment().
You can get your fragment by id/tag from fragment manager and do whatever you want with them.
that's super easy.
if you are adding your fragments in runtime (using FragmentManager)
you are creating objects of that fragment...just keep the reference with you and you are good to call any function or access any variable of the fragment.
e.g. you fragment is MyFragment
MyFragment mf = new MyFragment();
getSupportFragmentManger().beginTrans......you know the code to add a fragment
then simply call any method...for say... change() by mf.change();
or you can do something like
MyFragment mf = (MyFragment) findFragmentById(R.id.container);
and then again mf.change();
Objective: To use fragment arguments to pass along the string value from a TextView to a new fragments TextView, BUT while using a ViewPager with different layouts/fragments in the FragmentPagerAdapter.
Problem: The new fragment never receives the fragments arguments from the previous fragment.
My Set up: I have my Activity hosting the ViewPager and FragmentPagerAdapter. I have overridden FragmentPagerAdapters getItem(int position) method to create a new fragment instance depending on the current position of the ViewPager. Right now I only have two Fragments in my adapter, but will be adding more after getting over this obstacle. I am using the newInstance() technique to pass along the Fragment's arguments, but nothing is ever passed.
Pertinent Code:
My FragmentPagerAdapter code:
//this is a variable that I pass in as the newInstanct() methods parameter,
// I update this variable from my fragments constantly
public static String fragmentArgumentsValue = "";
mViewPager.setAdapter(new FragmentPagerAdapter(fm) {
#Override
public int getCount() {
return NUMBER_OF_VIEWS;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
Log.d(TAG, "---In getPosition(), position 0---");
return Frag1
.newInstance(fragmentArgumentsValue);
case 1:
Log.d(TAG, "---In getPosition(), position 1---");
return frag2
.newInstance(fragmentArgumentsValue);
default:
Log.d(TAG, "---In getPosition(), DEFAULT---");
return frag1
.newInstance(fragmentArgumentsValue);
}
}
});
One of the fragments newInstance() method:
public static Fragment newInstance(String fragmentArgumentsValue) {
Frag1 f = new Frag1();
Bundle bun = new Bundle();
bun.putString(KEY_FRAGMENT_ARGUMENTS_STRING, fragmentArgumentsValue);
f.setArguments(bun);
return f;
}
Getting the fragments arguments:
String argString = getArguments().getString(
KEY_FRAGMENT_ARGUMENTS_STRING);
if (argString.length() != 0) {
Log.d(TAG, "Trying to set the frag args to:" + argString);
mWorkingTextView.setText("" + argString);
} else {
Log.d(TAG, "Couldn't set frag args to: " + argString);
}
What I've Tried: I've tried giving the Activity that hosts the ViewPager and FragmentPagerAdapter a static variable that I constantly update in each one of my fragments, I include the static variable in the fragment.newInstance(staticVariableInActivity) method, but this doesn't seem to work.
I've also tried using the ViewPager callback viewPager.setOnPageChangeListener() I had overridden the onPageSelected(int pos) and tried to pass the fragment arguments there, nevertheless it didn't work... so please help me S.O.!!!
My thoughts: I do not have the different fragments and layouts in an ArrayList or any list for that matter, I just instantiate the Fragments via the newInstance() method depending on the positions of the FragementPagerAdapter, could this be a problem? Should I create a singleton list of the layouts/fragments? So I can change the values of the Fragments TextViews via the singleton list? (excuse me if that's a dumb or not possible thing to do).
Note: I am am saving away the TextViews values via public void onSaveInstanceState(Bundle outState) to handle screen rotations. Could this be a problem?
Alright so I this link will help in the communication between fragments: Communication with other Fragments .
You need to define a interface that the Activity will implement and the fragments will use to send data onward to the activity, and the activity will then find the fragment by tag doing something like this:
frag = (Fragment2) getSupportFragmentManager()
.findFragmentByTag(
"android:switcher:" + R.id.viewPager + ":2");
and then update the fragment by calling a public method implemented within the fragment.
The link provided will help greatly, it is from the Android Development website.
Adds the bundle inside your Adapter.
example in the constructor of the adapter :
public ViewPagerAdapter(Context context, FragmentManager fm) {
super(fm);
fragments.add(TileFragments.newInstance());
Bundle bundleFeatures = new Bundle();
bundleFeatures.putString(ContentName.FEATURES.toString(),ContentName.FEATURES.toString());
fragments.get(0).setArguments(bundleFeatures);
}