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);
}
Related
I'm kinda new to Android developpement and I use the 'Navigation' library.
Starting from my first fragment (which is a recyclerview that fetch data from an API), if I navigate to another fragment, the navigation controller destroy the first fragment and create the second fragment and show it. If I want to return to the first one (with the left arrow or the back button), it destroy de second fragment and create the first from scratch, making it reload all the data and using bandwith.
I have read many solutions for this but they are all fastidious :
using mvvm
write my own navigation controller
using mvp
I'd like to know what's the better way to retrieve data back without calling again my API.
My first fragment :
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
AnnoncesViewModel annoncesViewModel = new ViewModelProvider(this).get(AnnoncesViewModel.class);
root = inflater.inflate(R.layout.fragment_annonces, container, false);
ctx = root.getContext();
recyclerView = root.findViewById(R.id.listeannonce_rv);
annoncesViewModel.getAnnonces().observe(this, data-> {
recyclerViewAdapter = new ListeAnnoncesAdapter(data, ctx, AnnoncesFragment.this);
recyclerView.setLayoutManager(new LinearLayoutManager(root.getContext()));
recyclerView.setAdapter(recyclerViewAdapter);
});
return root;
}
The viewmodel :
public class AnnoncesViewModel extends ViewModel {
MutableLiveData<ArrayList<Annonce>> annonces;
ArrayList<Annonce> AnnonceArrayList;
public AnnoncesViewModel() {
annonces = new MutableLiveData<>();
AnnonceArrayList = new ArrayList<>();
annonces.setValue(AnnonceArrayList);
}
public MutableLiveData<ArrayList<Annonce>> getAnnonces() {
return annonces;
}
}
For navigation, i use
navController.navigate(R.id.frag1_to_frag2);
or
navController.navigate(R.id.nav_frag2);
But it doesn't change anything.
At the moment, the data is retrieved when I press a button.
Thanks for help !
The ViewModel approach is the right choice. The problem is that when you navigate to the new fragment, the AnnoncesViewModel is getting destroyed also because you are passing the Fragment context to the ViewModelProvider. To keep the ViewModel after navigating to other fragment pass Activity context to the the provider like:
ViewModelProviders.of(requireActivity()).get(AnnoncesViewModel::class.java)
This will keep the ViewModel "alive" when you launch again your Fragment instead of creating a new AnnoncesViewModel every time the Fragment is created.
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.
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);
}
I'm trying to follow this tutorial
I have a project that uses the Sidebar Navigation, so I have one MainActivity and multiple Fragments. At ~6:20 into the video, you can see the following code:
PersonListAdapter adapter = new PersonListAdapter(
this,
R.layout.adapter_view_layout,
peopleList);
The constructor for the PersonListAdapter Class is:
public PersonListAdapter(Context context, int resource, ArrayList<Attacks> objects) {
super(context, resource, objects);
this.mContext = mContext;
mResource = resource;
}
The problem lies with Context.
If I use the word "this", there is a red line.
If I replace
"this" with "getActivity()", there is no red line, but the app
crashes when I run it.
I've also tried "this.getContext()" and "this.getActivity()"
I have also tried replacing "this" with "getActivity().getApplicationContext()", and the app crashes.
The tutorial uses MainActivity.java, but my code is in FragmentCharacters.java. I don't know what I'm supposed to write in place of "this", or if I need to change something in the PersonListAdapter class for Context.
You cannot use a Fragment as a Context, because Fragment doesn't inherit from Context.
However, if you consult the Fragment lifecycle, you can see that the Fragment has access to its host Activity at any time between the lifecycle callbacks OnActivityCreated() and onDestroyView(). If you try to access the Activity before OnActivityCreated(), for example, it will probably return null.
So make sure you are calling getActivity() from within onActivityCreated() or later, which will make sure your Activity is available.
UPDATE, I Provided a Case Example inside the Code Snippets as well, and I chose "FragmentName" as Fragment name for example.
First Look at This Fragment Structure.
I Added [mAdapter] in Both onCreate and onCreateView
And I Added FragmentName.this for the Forth argument
The Reason is, You can send data from The Adapter to Other Activities with it, for Example FragmentName.mAdapter.getLayoutPosition()
But, Let's assume We have an ImageView which is In MainActivity and we want to use it in our Adapter, So let's Establish an ImageView In our Fragment as well, Notice I Declared the ImageView Inside onCreate
And, For Another Example, Let's Assume we Have a Public Void at the End of our Fragment as Well, It can be Literally Anything. I Named it ExampleClass
/////////FIRST TAKE A LOOK AT FRAGMENT//////////
public class FragmentName extends Fragment {
PersonListAdapter adapter;
ImageView imageView; // For Example thi ImageView is from MainActivity
public FragmentName() {
...
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
adapter = new PersonListAdapter(getContext(), R.layout.adapter_view_layout, peopleList, FragmentName.this);
imageView = (ImageView) getActivity().findViewById(R.id.ImageView);
// This Imageview is in Another Activity, Like MainActivity
// So we Need to Find it Using 'getActivity()'
...
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
adapter = new PersonListAdapter(getContext(), R.layout.adapter_view_layout, peopleList, FragmentName.this);
}
}
public void ExampleClass(int color, ...) {
...
}
////////////////////////////////////////////////////////////////
Now, Let's take this Example into our Adapter as well, to Show how it can be Used.
But, In the Adapter Use [FragmentName], Instead of [Fragment] like Below:
///////////NOW INSIDE YOUR ADAPTER/////////////
public class PersonListAdapter extends RecyclerView.Adapter < PersonListAdapter.myViewHolder > {
FragmentName myFragment; // SEE WHAT HAPPENDED HERE?
...
public PersonListAdapter(Context context, int resource, ArrayList < Attacks > objects, FragmentName fragment) {
super(context, resource, objects);
this.mContext = mContext;
mResource = resource;
this.myFragment = fragment
}
#Override
public PersonListAdapter.myViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Example use of myFragment
// Lets Execute ExmpleClass inside the Fragment
myFragment.ExampleClass(int color, ...);
// Let's Use the ImageView from MainActivity Here
myFragment.imageView.setImageRresource(...);
...
}
...
// YOU CAN NOW USE "myFragment" As a Context In your Adapter
The Good Part about this is That You can Use Fragment As CONTEXT in Your PersonListAdapter
Update: The second Code, onCreateViewHolder is wrong, it has to be inside a ClickListener in ViewHolder or onBindViewHolder
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();