Retrieve data from previous instance with navigation controller - java

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.

Related

Array from activity to fragment to show list

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);
}

Access a Database fromout a Fragment to DIsplay in List

Ok so i tried to learn of all These other questions here, but i am not getting the hang of it, so i decided to ask by myself.
I got a Main Activity with different Fragments for changing the views (used the Android Standard sidebar activity template). From there the user creates Tasks via a separate Acitivty which Returns the values. In the Main Activity the Tasks are getting stored into the SQLite-database. So far it works.
Now i have the Fragment TaskList with the corresponding layout TaskList_main.xml. In this XML file i got a simple ListView which i want to fill with the values from the database. My Problem is where to write that method and when to Access it. the method would be
public void showAllListEntries() {
List<TaskData> TaskList = datasource.getAllTasks();
//Daten werden im ArrayAdapter gespeichert
ArrayAdapter<TaskData> TaskListArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, TaskList);
ListView TaskDataListView = (ListView) findViewById(R.id.TaskListView);
TaskDataListView.setAdapter(TaskListArrayAdapter);
}
My Fragment is empty like this atm
public class TaskList extends Fragment {
View view;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.activity_main_tasklist, container, false);
return view;
}
}
I can also send the main Activity if you like but it's a bit messy
So can someone tell me how i get this to work? And did i explain my Problem clearly?
You can do it in onCreateView. But you should make an async call to get the tasks to display and have your fragment as a listener. When you get the tasks you can create your adapter and attach it to the ListView.
And you should have a ProgressBar in TaskList_main.xml (which should be renamed to task_list_fragment.xml, I don't think there is a naming convention for layouts, but this is quite used) and hide it when you receive the data.

Adding onclickListeners to view elements outside of activity (own class)

First of all i'm not even sure if this is possible.
I'm trying the achieve the following: Create my own seperate dependency which dynamically adds onclick listeners to certain elements for which the id's are known. (Entirely outside an activity; see the following:)
public class Example extends Application
What i managed so far is accessing specific view elements from within my own class. For this i am using the LayoutInflator:
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.activity_main, null);
View element = view.findViewById(R.id.readthis);
After retrieving the specific view element i add an onclickListener (in this example a button)
button.setOnClickListener(getOnClickDoSomething(button));
View.OnClickListener getOnClickDoSomething(final Button button) {
return new View.OnClickListener() {
public void onClick(View v) {
button.setText("text now set.. ");
}
};
}
Setting the thing works fine. But i'm guessing the layout inflator actually creates a copy of the view and i'm not getting direct references to the currently active view objects. Which means the onclickListener isn't being triggered because it's not set to the proper object.
I'm fairly new to Android and hence the question whether i'm wasting my time or if there's a way to get this to work. Would love to get some advice on this.
I'm not sure if I got your question right, but you can expose setter methods in the activity that contains the relevant views, to set a click listener from outside. Something like this:
public void setViewClickListener(OnClickListener clickListener) {
if (myView != null) {
myView.setOnClickListener(clickListener);
}
}
And then in your separate class, call this method with the relevant click listener.
Resolved! I was looking at this the wrong way. Solved it by implementing Application.ActivityLifecycleCallbacks and registering my class by registerActivityLifecycleCallbacks(this)
Now i can access the views from the activity context within the callbacks.

Does fragment call the onCreate method of parent activity?

I have an activity with a viewPager inside of it, and a static ArrayList of integers that I am shuffling using Collections.shuffle(list) in the activity's onCreate method, this viewPager's fragments are using the ArrayList in parent activity.
The problem is that whenever a new fragment instantiated of the viewPager the onCreate method of parent activity is called, and I don't want that to happen because I want the list to have the same data in all fragments and not reshuffled. Do fragments call the onCreate method of their parent activities everytime there is a new instance? if Yes how can I work around this to keep the list from shuffling every time?
CODE:
Activity Code:
public static final ArrayList<Integer> IDs = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
IDs.add(0);
IDs.add(1);
IDs.add(2);
Collections.shuffle(IDs);
setContentView(R.layout.activity_walkthrough);
pager = (ViewPager) findViewById(R.id.pager);
adapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
pager.setAdapter(adapter);
Fragment Code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = (View) inflater.inflate(
R.layout.fragment_walkthrough, container, false);
final TypedArray imgs = getResources().obtainTypedArray(R.array.walkthrough_images);
ImageView imageView = (ImageView) v.findViewById(R.id.image);
if (page == 0) {
imageView.setImageResource(imgs.getResourceId(Walkthrough.IDs.get(0), 0));
} else if (page == 2) {
imageView.setImageResource(imgs.getResourceId(Walkthrough.IDs.get(1), 0));
} else {
imageView.setImageResource(imgs.getResourceId(Walkthrough.IDs.get(2), 0));
}
return v;
}
Now I want the ArrayList "IDs" to always have the same data and order when ever I instantiate a new fragment but it is not working, every time I create a new fragment the method onCreate gets recalled and a reshuffle happens!
Fragments are added to activity and therefore fragments get affected by activity.
Activity can cause calling any fragment callback method, but fragment can't
The lifecycle of the activity in which the fragment lives directly affects the lifecycle of the fragment.
For example, when the activity receives onPause(), each fragment in the activity receives onPause().
Fragments have a few extra lifecycle callbacks, however, that handle unique interaction with the activity in order to perform actions such as build and destroy the fragment's UI.
These additional callback methods are like onAttach(), onCreateView(), etc.
It'll clears the somewhat relation between fragment and activity.
Thanks

Use same fragment in ViewPager but fragment will have different layout each time

I want to keep my application thin.
Problem: I would like to reuse my Fragment class code to create 3 different instances in the ViewPager which will have 3 pages. Each Fragment will have a different ImageView or background Drawable. What are best practices regarding this? I noticed that using factory methods like here seem to be good, any other alternatives?
I have one Fragment which has the following methods:
Fragment.java
public static Fragment newInstance(Context context) {
FragmentTutorial f = new FragmentTutorial();
Bundle args = new Bundle();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment, null);
return root;
}
I have a ViewPagerAdapter class which has the following methods:
ViewPagerAdapter.java
public ViewPagerAdapter(Context context, FragmentManager fm) {
super(fm);
mContext = context;
}
#Override
public Fragment getItem(int position) {
return new FragmentTutorial().newInstance(mContext);
}
#Override
public int getCount() {
return totalPage;
}
What I've found is the "best" way to do it (in my opinion, of course) is to do the following:
Have the fragment contain methods to set the customizable data (background, text, etc)
Note: Be careful of trying to load the data in when first creating the fragment. You may be able to set the data before onCreateView() even runs, or at other times it may run after onCreateView(). I personally use a boolean to check if the data has been set. Inside onCreateView() [or onActivityCreated()], I check if the data has been set already. If it has, load in the data. Alternatively, while setting the data, I check if the views have been created/cached already. This is done by simply having variables to cache the data, say private ImageView mBackgroundView. If the view is not null, then I safely set the data on the views.
The above is also an alternative to using newInstance, although both methods work pretty well. However, for more flexibility, I only use newInstance if a) the data is already known before the fragment has to be inserted and b) the data doesn't need to change according to input from elsewhere much.
Let the ViewPager handle all the data
Pass in all the data - a list of ImageViews, a array of Strings, define where all the data is in Resources, etc - at the very beginning [say, in the constructor]
Have the ViewPager create an ArrayList of the fragments- set up each fragment as early as possible (say when first getting all the data) and add it to the list
Let getCount() just use the size of the list
Let getItem() just get the item in the list at the position
Note: If you have any dynamic data, set it up in the getItem() method. Furthermore, you can always add more data+fragments during runtime as well [just notify the adapter that the dataset has been changed]
Essentially, the fragment is like a simple servant- it does simply the least work necessary. If it doesn't have to handle choosing the data, all the better. It'll thus be far more flexible. Just give methods to set the data/views appropriately on the fragment. Now, the ArrayAdapter can do all the grimy hard work with managing the data and giving it to the appropriate fragment. Take advantage of that.
Now, note that this is assuming you want to use a single layout but want to change different aspects of that layout (texts, background, etc). If you want to make a master fragment class that can use any sort of defined layout, you can but note that it decreases the runtime flexibility (how can you change the text or background to something you get from the internet? You simply can't if you only can define and choose from pre-set layouts).
Either way, the ArrayAdapter should take care of all the different data while the fragment simply does as it's designed to do, in a more flexible manner preferably.
Edit:
Here is the project where I most recently implemented this sort of pattern. Note that it has far more to it, so I'll replace it with some not-so-pseudo pseudo-code in the morning/afternoon.
ViewPager [a bit sloppy with all the different things I was trying to do, including extending from a FragmentStatePagerAdapter without actually using any of the specific features of a StatePagerAdapter. In other words, I still need to work on the lifecycle implementations everywhere]
Fragment [Also may be a bit sloppy but shows the pattern still]
The object (actually another fragment) that uses the ViewPager [it's actually a "VerticalViewpager" from a library, but other than the animations and direction to change the current fragment, it's exactly the same- particularly code-wise]
Edit2:
Here is a more (if overly) simplified example of the pattern described above.
Disclaimer: The following code has absolutely no lifecycle management implementations and is older code that has been untouched since around August '14
Fragment simply allows the user of the fragment to set the background color and the text of the single TextView
Link to BaseFragment
Link to layout file
The adapter creates three instances of the fragment and sets the background color and text of each. Each fragment's text, color, and total fragments is hard coded.
Link to Activity+adapter
Link to layout file
Now, here are the exact relevant portions of the code:
BaseFragment
// Note: Found out later can extend normal Fragments but must use v13 adapter
public class BaseFragment extends android.support.v4.app.Fragment {
FrameLayout mMainLayout; // The parent layout
int mNewColor = 0; // The new bg color, set from activity
String mNewText = ""; // The new text, set from activity
TextView mMainText; // The only textview in this fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the fragment's layout
View view = inflater.inflate(R.layout.fragment_base,container,false);
// Save the textview for further editing
mMainText = (TextView) view.findViewById(R.id.textView);
// Save the framelayout to change background color later
mMainLayout = (FrameLayout) view.findViewById(R.id.mainLayout);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// If there is new text or color assigned, set em
if(mNewText != ""){
mMainText.setText(mNewText);
}
if(mNewColor != 0){
mMainLayout.setBackgroundColor(mNewColor);
}
}
#Override
public void onStart(){
super.onStart();
}
// Simply indicate to change the text of the fragment
public void changeText(String newText){
mNewText=newText;
}
// Simply indicate to change the background color of the fragment
public void changeBG(int color) {
// If no color was passed, then set background to white
if(color == 0)
{
mNewColor=getResources().getColor(R.color.white);
}
// else set the color to what was passed in
else{
mNewColor=color;
}
}
}
MyAdapter
class MyAdapter extends FragmentPagerAdapter{
// Three simple fragments
BaseFragment fragA;
BaseFragment fragB;
BaseFragment fragC;
public MyAdapter(FragmentManager fm) {
super(fm);
}
public void setFragments(Context c){
// Set up the simple base fragments
fragA = new BaseFragment();
fragB = new BaseFragment();
fragC = new BaseFragment();
Resources res = c.getResources();
fragA.changeText("This is Fragment A!");
fragB.changeText("This is Fragment B!");
fragC.changeText("This is Fragment C!");
fragA.changeBG(res.getColor(R.color.dev_blue));
fragB.changeBG(res.getColor(R.color.dev_green));
fragC.changeBG(res.getColor(R.color.dev_orange));
}
#Override
public Fragment getItem(int position) {
// TODO: Make this more efficient, use a list or such, also comment more
Fragment frag = null;
if(position == 0){
frag = fragA;
}
else if(position == 1){
frag = fragB;
}
else if(position == 2){
frag = fragC;
}
return frag;
}
#Override
public int getCount() {
return 3;
}
}
You need to pass some sort of id along with newInstance() while creating instance. And according to that id you can use if..else to choose layout file.
See my reference code below:
int id;
public static Fragment newInstance(Context context, int id) {
FragmentTutorial f = new FragmentTutorial();
Bundle args = new Bundle();
this.id = id;
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(id == 1)
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment1, null);
else
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment2, null);
return root;
}
Can't you just introduce fields to the Fragment class to account for the variances in background, etc. and add them to its constructor? Then in getItem instantiate the Fragment class with different values depending on the value of position.

Categories