Passing Objects to fragment without argument - android

What will be the implications if I passed an object to a fragment without using bundle? I just opened one of the old code and found it there and couldn't help asking this question as I have never tried it. Also I am not finding any memory leaks in it.
This is how it is implemented -
Activity class:
MyFragment fragment =
MyFragment.newInstance(getIntent().getStringExtra(DATA),
instance.getCallback(),
instance.getRequest());
getSupportFragmentManager().beginTransaction().replace(R.id.content, fragment).commit();
Fragment class:
public class MyFragment extends Fragment {
public MyFragment() {
/* Required empty public constructor */
}
public static MyFragment newInstance(String data, Callback callback,
Request request) {
MyFragment fragment = new MyFragment();
fragment.setCallback(callback);
fragment.setRequest(request);
fragment.setData(data);
return fragment;
}
private void setCallback(Callback callback) {
this.callback = callback;
}
private void setData(Data data) {
this.data = data;
}
private void setRequest(Request request) {
this.request = request;
}
}
Generally, what I have used till date is Fragment#setArguments(Bundle)

Please check out this answer: Why its not recommended to pass object to fragment with simple getter setter.
Long story short, you will lose your data on configuration changes.
There are mainly two ways of communicating with a fragment: via bundles or via an interface that you implement in your activity. Please see this link in order to see how to properly communicate with a fragment via an interface: https://developer.android.com/training/basics/fragments/communicating

Related

How to update Fragments in Tablayout? (Viewpager2, FragmentStateAdapter)

I create the initial content with the data from an arraylist(i get it from firebase)
the data changes every 15 minutes, how do i call a recreation or how do i settext from the mainactivity to the fragments / how do i update the textviews in the fragments (any of those would work please)
i've read a looot of people solving this with getItem but remember i have FragmentStateAdapter i don't have those methods viewPager2.getAdapter().notifyDataSetChanged() does nothing
Overridable Methods in FragmentStateAdapter
and then i thougth how about an interface just to change the text in my textviews but it always calls an error about a null pointer because i understand that the fragment is being "paused" while i don't see it because of the cycle and so and so
Calling a recreation of the viewPager2.setAdapter(new PagerAdapter() works but only when the gods want? and i want something to force it or to be relaiable
(Sincerelly i don't know anymore why am i using the newest viewpager2 and PagerAdapter FragmentStateAdapter as Google suggests) should i try my way around viewpager and all those old tools?
As per Doc we can update the fragment by calling notifyDatasetChanged() therefore by creating a function(setData) in Adapter and inside which calling notifyDatasetChanged() may be work..like below
fun addData(data: ArrayList<>) {
mData = data
notifyDataSetChanged()
}
I encountered this problem, my solution is: save the fragment instance using WeakReference in adapter, and invoke the method in fragment when needing to update it's data.
The code is like this:
public MyAdapter extends FragmentStateAdapter{
private Map<Integer, WeakReference<MyFragment>> fragmentMap = new HashMap();
#Override
public Fragment createFragment(int position) {
MyFragment fragment=new MyFragment();
fragmentMap.put(position, new WeakReference<>(fragment));
return fragment;
}
public void updateData(List<String> dataList){
for (int position : fragmentMap.keySet()) {
WeakReference<MyFragment> wr = fragmentMap.get(position);
if (wr != null && wr.get() != null) {
MyFragment fragment = wr.get();
fragment.updateData(dataList.get(position));
}
}
}
}
public MyFragment extends Fragment{
public void updateData(String data){
...
}
}
Now you can invoke updateData(List dataList) on your adapter to update data, and it is using WeakReference to avoid memory leak in the code.

Passing objects between fragments using parcelable in kotlin

How do you do that? I have the object class implementing parcelable but i don't know what to do for sending the object from one fragment to another one. Help me please.
You can use the navGraph to share data between fragments.It's easy.
Sharing data between fragments is always painful, as both fragments need to define same interface description and the owner activity must bind two together.
And also need to handle the conditions like other fragment not created or not visible
But with new ViewModel, our life become easy to deal with fragment communication. All we have to do is just create a common ViewModel using the activity scope to handle the communication.
Let’s take an example where as in one fragment we need to show list of news articles , and another to show details of the selected news article.
Step1:- Create the Article model class.
public class Article {
private int articleID;
private String articleName;
private String details;
public int getArticleID() {
return articleID;
}
public void setArticleID(int articleID) {
this.articleID = articleID;
}
public String getArticleName() {
return articleName;
}
public void setArticleName(String articleName) {
this.articleName = articleName;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
}
Step2:- Create a ArticleViewModel which holds the objects.
public class ArticleViewModel extends ViewModel {
private LiveData<List<Article>> articleList;
private final MutableLiveData<Article> selectedArticle = new MutableLiveData<Article>();
public MutableLiveData<Article> getSelectedArticle() {
return selectedArticle;
}
public void setSelectedArticle(Article article) {
selectedArticle.setValue(article);
}
public LiveData<List<Article>> getArticleList() {
return articleList;
}
public void loadArticles() {
// fetch articles here asynchronously
}
}
Step3:- Create a ArticleListFragment which take care of your list.
public class ArticleListFragment extends Fragment {
private SharedViewModel model;
public void onActivityCreated() {
ArticleViewModel model = ViewModelProviders.of(getActivity()).get(ArticleViewModel.class);
listItemSelector.setOnClickListener(article -> {
model.setSelectedArticle(article);
});
}
}
Step4:- Create your ArticleDetailFragment to show details of article
public class ArticleDetailFragment extends LifecycleFragment {
public void onActivityCreated() {
ArticleViewModel model = ViewModelProviders.of(getActivity()).get(ArticleViewModel.class);
model.getSelectedArticle().observe(this, { article ->
// update UI
});
}
}
If you observe, both fragments are using getActivity() while getting the ViewModelProviders. Means both fragments receive same ArticleViewModel instance, which is scoped to your parent Activity.
Its just that simple and we get more benefits like
Your Activity no need to worry about this communication
Even one fragment get destroyed, other one use the data in ViewModel.
Happy coding :)

How do we cast context to fragment reference?

I have a class 'Common' and a fragment 'FragmentTest'. The 'Common.java' is a general class that have some common functions for other activities..Those functions are accessed by context of each activities.. And here I am passing the fragment's context to a function in that class. I am doing like this
In Fragment :-
Common commonObj = new Common();
commonObj.myfunction(this.getActivity(),"Do you want to Update ?");
And in Class after some operation i'm trying to return back to fragment class.Like this
:-
public void myfunction(Context context , String str){
//....//
if(context.getClass().isInstance(FragmentTest.class)){
**FragmentTest mContext = (FragmentTest)context;**
mContext.FunctionInFragment();
}
}
But i have error in this..Because i cannot cast the context to fragment reference.
Somebody please help..
Firstly you can't cast a Context to a Fragment as Fragment doesn't extend Context. Activity does extend Context which is why when you do this from an Activity what you are trying works.
I'd suggest ideally that the methods in your Common class should be totally unaware of the existence of your Fragment. This way they are not 'coupled' together. To achieve this you can use a callback interface.
Create an interface as follows:
public interface Callback<T> {
onNext(T result);
}
Then you can change your method in Common to the following:
public void myfunction(Callback<Void> callback , String str){
//....//
callback.onNext(null);
}
Then when you call the method in Common from the Fragment you would do it like this:
Common commonObj = new Common();
commonObj.myfunction(
new Callback<Void>() {
#Override
public void onNext(Void result) {
functionInFragment();
}
},
"Do you want to Update ?"
);
If you needed to send data back to the function then you can change the return type of the callback. For instance if you wanted to pass back a string you would use Callback<String> and then the method in the original call would look like this:
new Callback<String>() {
#Override
public void onNext(String result) {
}
}
And in your Common class you would call it like this:
public void myfunction(Callback<String> callback , String str){
//....//
String result = "Hello from common";
callback.onNext(result);
}
You can do something like this:
public void myfunction(BaseFragment fragment, String str){
//....//
if(fragment.getClass().isInstance(FragmentTest.class)){
FragmentTest fr = (FragmentTest) fragment;
fr.FunctionInFragment();
}
}
i.e. using some base fragment(BaseFragment) and inherit from it.

Passing objects in to Fragments

I've been working with lots of Fragments recently and have been using two distinct methods of passing in objects to the Fragments, but the only difference that I can see is that in the approach taken by FragmentOne below, the object you pass in must implement the Serializable interface (and everything associated with that).
Are there any benefits to using one over the other?
public class FragmentOne extends Fragment {
public static final String FRAGMENT_BUNDLE_KEY =
"com.example.FragmentOne.FRAGMENT_BUNDLE_KEY";
public static FragmentOne newInstance(SomeObject someObject) {
FragmentOne f = new FragmentOne();
Bundle args = new Bundle();
args.putSerializable(FRAGMENT_BUNDLE_KEY, someObject);
f.setArguments(args);
return f;
}
public SomeObject getSomeObject() {
return (SomeObject) getArguments().getSerializable(FRAGMENT_BUNDLE_KEY);
}
}
and
public class FragmentTwo extends Fragment {
SomeObject mSomeObject;
public static FragmentTwo newInstance(SomeObject someObject) {
FragmentTwo fragment = new FragmentTwo();
fragment.setSomeObject(someObject);
return fragment;
}
public void setSomeObject(SomeObject someObject) {
mSomeObject = someObject;
}
}
There are 3 ways to pass objects to a fragment
They are:
Passing the object through a setter is the fastest way, but state will not be restored automatically.
setArguments with Serializable objects is the slowest way (but okay for small objects, I think) and you have automatic state restoration.
Passing as Parcelable is a fast way (prefer it over 2nd one if you have collection of elements to pass), and you have automatic state restoration.
http://developer.android.com/reference/android/os/Parcelable.html
for Collection such as List :
I wanted to share my experience.
you need to implement Parcelable
Just use the putParcelableArrayList method.
ArrayList<LClass> localities = new ArrayList<LClass>;
...
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(KEY_LClass_LIST, localities);
fragmentInstance.setArguments(bundle);
return fragmentInstance;
And retrieve it using...
localities = savedInstanceState.getParcelableArrayList(KEY_LCLass_LIST);
So, unless you need the custom ArrayList for some other reason, you can avoid doing any of that extra work and only implement Parcelable for your Locality class.

Best practice for instantiating a new Android Fragment

I have seen two general practices to instantiate a new Fragment in an application:
Fragment newFragment = new MyFragment();
and
Fragment newFragment = MyFragment.newInstance();
The second option makes use of a static method newInstance() and generally contains the following method.
public static Fragment newInstance()
{
MyFragment myFragment = new MyFragment();
return myFragment;
}
At first, I thought the main benefit was the fact that I could overload the newInstance() method to give flexibility when creating new instances of a Fragment - but I could also do this by creating an overloaded constructor for the Fragment.
Did I miss something?
What are the benefits of one approach over the other? Or is it just good practice?
If Android decides to recreate your Fragment later, it's going to call the no-argument constructor of your fragment. So overloading the constructor is not a solution.
With that being said, the way to pass stuff to your Fragment so that they are available after a Fragment is recreated by Android is to pass a bundle to the setArguments method.
So, for example, if we wanted to pass an integer to the fragment we would use something like:
public static MyFragment newInstance(int someInt) {
MyFragment myFragment = new MyFragment();
Bundle args = new Bundle();
args.putInt("someInt", someInt);
myFragment.setArguments(args);
return myFragment;
}
And later in the Fragment onCreate() you can access that integer by using:
getArguments().getInt("someInt", 0);
This Bundle will be available even if the Fragment is somehow recreated by Android.
Also note: setArguments can only be called before the Fragment is attached to the Activity.
This approach is also documented in the android developer reference: https://developer.android.com/reference/android/app/Fragment.html
The only benefit in using the newInstance() that I see are the following:
You will have a single place where all the arguments used by the fragment could be bundled up and you don't have to write the code below everytime you instantiate a fragment.
Bundle args = new Bundle();
args.putInt("someInt", someInt);
args.putString("someString", someString);
// Put any other arguments
myFragment.setArguments(args);
Its a good way to tell other classes what arguments it expects to work faithfully(though you should be able to handle cases if no arguments are bundled in the fragment instance).
So, my take is that using a static newInstance() to instantiate a fragment is a good practice.
There is also another way:
Fragment.instantiate(context, MyFragment.class.getName(), myBundle)
While #yydl gives a compelling reason on why the newInstance method is better:
If Android decides to recreate your Fragment later, it's going to call
the no-argument constructor of your fragment. So overloading the
constructor is not a solution.
it's still quite possible to use a constructor. To see why this is, first we need to see why the above workaround is used by Android.
Before a fragment can be used, an instance is needed. Android calls YourFragment() (the no arguments constructor) to construct an instance of the fragment. Here any overloaded constructor that you write will be ignored, as Android can't know which one to use.
In the lifetime of an Activity the fragment gets created as above and destroyed multiple times by Android. This means that if you put data in the fragment object itself, it will be lost once the fragment is destroyed.
To workaround, android asks that you store data using a Bundle (calling setArguments()), which can then be accessed from YourFragment. Argument bundles are protected by Android, and hence are guaranteed to be persistent.
One way to set this bundle is by using a static newInstance method:
public static YourFragment newInstance (int data) {
YourFragment yf = new YourFragment()
/* See this code gets executed immediately on your object construction */
Bundle args = new Bundle();
args.putInt("data", data);
yf.setArguments(args);
return yf;
}
However, a constructor:
public YourFragment(int data) {
Bundle args = new Bundle();
args.putInt("data", data);
setArguments(args);
}
can do exactly the same thing as the newInstance method.
Naturally, this would fail, and is one of the reasons Android wants you to use the newInstance method:
public YourFragment(int data) {
this.data = data; // Don't do this
}
As further explaination, here's Android's Fragment Class:
/**
* Supply the construction arguments for this fragment. This can only
* be called before the fragment has been attached to its activity; that
* is, you should call it immediately after constructing the fragment. The
* arguments supplied here will be retained across fragment destroy and
* creation.
*/
public void setArguments(Bundle args) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mArguments = args;
}
Note that Android asks that the arguments be set only at construction, and guarantees that these will be retained.
EDIT: As pointed out in the comments by #JHH, if you are providing a custom constructor that requires some arguments, then Java won't provide your fragment with a no arg default constructor. So this would require you to define a no arg constructor, which is code that you could avoid with the newInstance factory method.
EDIT: Android doesn't allow using an overloaded constructor for fragments anymore. You must use the newInstance method.
Some kotlin code:
companion object {
fun newInstance(first: String, second: String) : SampleFragment {
return SampleFragment().apply {
arguments = Bundle().apply {
putString("firstString", first)
putString("secondString", second)
}
}
}
}
And you can get arguments with this:
val first: String by lazy { arguments?.getString("firstString") ?: "default"}
val second: String by lazy { arguments?.getString("secondString") ?: "default"}
I disagree with yydi answer saying:
If Android decides to recreate your Fragment later, it's going to call
the no-argument constructor of your fragment. So overloading the
constructor is not a solution.
I think it is a solution and a good one, this is exactly the reason it been developed by Java core language.
Its true that Android system can destroy and recreate your Fragment. So you can do this:
public MyFragment() {
// An empty constructor for Android System to use, otherwise exception may occur.
}
public MyFragment(int someInt) {
Bundle args = new Bundle();
args.putInt("someInt", someInt);
setArguments(args);
}
It will allow you to pull someInt from getArguments() latter on, even if the Fragment been recreated by the system. This is more elegant solution than static constructor.
For my opinion static constructors are useless and should not be used. Also they will limit you if in the future you would like to extend this Fragment and add more functionality to the constructor. With static constructor you can't do this.
Update:
Android added inspection that flag all non-default constructors with an error.
I recommend to disable it, for the reasons mentioned above.
I'm lately here. But somethings I just known that might help you a bit.
If you are using Java, there is nothing much to change. But for Kotlin developers, here is some following snippet I think that can make you a basement to run on:
Parent fragment:
inline fun <reified T : SampleFragment> newInstance(text: String): T {
return T::class.java.newInstance().apply {
arguments = Bundle().also { it.putString("key_text_arg", text) }
}
}
Normal call
val f: SampleFragment = SampleFragment.newInstance("ABC")
// or val f = SampleFragment.newInstance<SampleFragment>("ABC")
You can extend the parent init operation in child fragment class by:
fun newInstance(): ChildSampleFragment {
val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
// Do anything with the current initialized args bundle here
// with child.arguments = ....
return child
}
Happy coding.
Best practice to instance fragments with arguments in android is to have static factory method in your fragment.
public static MyFragment newInstance(String name, int age) {
Bundle bundle = new Bundle();
bundle.putString("name", name);
bundle.putInt("age", age);
MyFragment fragment = new MyFragment();
fragment.setArguments(bundle);
return fragment;
}
You should avoid setting your fields with the instance of a fragment. Because whenever android system recreate your fragment, if it feels that the system needs more memory, than it will recreate your fragment by using constructor with no arguments.
You can find more info about best practice to instantiate fragments with arguments here.
Since the questions about best practice, I would add, that very often good idea to use hybrid approach for creating fragment when working with some REST web services
We can't pass complex objects, for example some User model, for case of displaying user fragment
But what we can do, is to check in onCreate that user!=null and if not - then bring him from data layer, otherwise - use existing.
This way we gain both ability to recreate by userId in case of fragment recreation by Android and snappiness for user actions, as well as ability to create fragments by holding to object itself or only it's id
Something likes this:
public class UserFragment extends Fragment {
public final static String USER_ID="user_id";
private User user;
private long userId;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
userId = getArguments().getLong(USER_ID);
if(user==null){
//
// Recreating here user from user id(i.e requesting from your data model,
// which could be services, direct request to rest, or data layer sitting
// on application model
//
user = bringUser();
}
}
public static UserFragment newInstance(User user, long user_id){
UserFragment userFragment = new UserFragment();
Bundle args = new Bundle();
args.putLong(USER_ID,user_id);
if(user!=null){
userFragment.user=user;
}
userFragment.setArguments(args);
return userFragment;
}
public static UserFragment newInstance(long user_id){
return newInstance(null,user_id);
}
public static UserFragment newInstance(User user){
return newInstance(user,user.id);
}
}
use this code 100% fix your problem
enter this code in firstFragment
public static yourNameParentFragment newInstance() {
Bundle args = new Bundle();
args.putBoolean("yourKey",yourValue);
YourFragment fragment = new YourFragment();
fragment.setArguments(args);
return fragment;
}
this sample send boolean data
and in SecendFragment
yourNameParentFragment name =yourNameParentFragment.newInstance();
Bundle bundle;
bundle=sellDiamondFragments2.getArguments();
boolean a= bundle.getBoolean("yourKey");
must value in first fragment is static
happy code
Ideally we shouldn't pass anything in the fragment constructor, fragment constructor should be empty or default.
Now the second question is, what if we want to pass interface variable or parameters-
We should use Bundle to pass data.
For Interface we can putParceble in bundle and make that interface implement parceble
If possible we can implement that interface in activity and in fragment we can initialize listener in OnAttach where we have context[ (context) Listener].
So that during configuration change (e.g. Font change) the Activity recreation listener won't go uninitialize and we can avoid a null pointer exception.
Best way to instantiate the fragment is use default Fragment.instantiate method or create factory method to instantiate the the fragment
Caution: always create one empty constructor in fragment other while restoring fragment memory will throw run-time exception.
You can use smth like this:
val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, YourFragment::class.java.name)
because this answer now is Deprecated
Create an instance of the fragment using kotlin code.
Write in activity
val fragment = YourFragment.newInstance(str = "Hello",list = yourList)
Write in fragment
fun newInstance(str: String, list: ArrayList<String>): Fragment {
val fragment = YourFragment()
fragment.arguments = Bundle().apply {
putSerializable("KEY_STR", str)
putSerializable("KEY_LIST", list)
}
return fragment
}
Using the same fragment retrieve the data from bundle
val str = arguments?.getString("KEY_STR") as? String
val list = arguments?.getSerializable("KEY_LIST") as? ArrayList<String>
setArguments() is useless. It only brings a mess.
public class MyFragment extends Fragment {
public String mTitle;
public String mInitialTitle;
public static MyFragment newInstance(String param1) {
MyFragment f = new MyFragment();
f.mInitialTitle = param1;
f.mTitle = param1;
return f;
}
#Override
public void onSaveInstanceState(Bundle state) {
state.putString("mInitialTitle", mInitialTitle);
state.putString("mTitle", mTitle);
super.onSaveInstanceState(state);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
if (state != null) {
mInitialTitle = state.getString("mInitialTitle");
mTitle = state.getString("mTitle");
}
...
}
}
I believe I have a much simpeler solution for this.
public class MyFragment extends Fragment{
private String mTitle;
private List<MyObject> mObjects;
public static MyFragment newInstance(String title, List<MyObject> objects)
MyFragment myFrag = new MyFragment();
myFrag.mTitle = title;
myFrag.mObjects = objects;
return myFrag;
}

Categories

Resources