Since Fragments need to survive configuration change, it is strongly suggested by Android that we should implement our own newInstance() method when creating a fragment, passing necessary data to its Bundle instead of using new MyFragment().
Now, the problem is, I want to pass a callback (interface) to this Fragment.
At first I tried to make my interface extends Serializable, and simply use args.putSerializable("myInterface", myInterface).
But when Android try to save its state (parcel it), an Exception is thrown (Parcelable encountered IOException writing serializable object).
I know the official way to do this is to make the caller Activity implements the callback, and reference the callback to the activity during onAttach() of the fragment.
Yes, it works, but it feels weird, because I am actually creating a library and now instead of asking user to pass a callback to the builder, I have to tell them your caller activity must implements a certain interface.
More importantly, what if I want to show this Fragment from another Fragment? onAttach() always attach to an Activity.
So, is there a way that I can let user pass the callback to the fragment, and being able to survive configuration change (e.g. device rotation)?
UPDATE
As requested, here is the interface that I want to pass:
public interface OnImageSelectedListener extends Serializable {
void onImageSelected (String uri);
}
And how I construct this interface:
new OnImageSelectedListener() {
#Override
public void onSingleImageSelected(String uri) {
Glide.with(MainActivity.this).load(uri).into(ivImage);
}
}
At last, I come to the conclusion:
We should not retain the callback.
Imagine the callback does the below code:
Glide.with(context).load(image).into(imageView);
When device rotates, the above imageView, which belongs to an activity or another fragment, is also destroyed and re-created. The old ImageView that is referenced in the retained callback no longer exists.
This will either make Glide throw an exception; or leaks the old ImageView and thus the whole Activity.
That's why we should always reference the callback to an attaching Activity.
And if the caller is a Fragment, instead of referencing the callback in onAttach(), simply reference the parent fragment in onCreateView():
if (getParentFragment() != null && getParentFragment() instanceof YourInterface) {
yourCallback = (YourInterface) getParentFragment();
}
So if to answer the original question,
Can I pass an interface to bundle of a Fragment?
The answer is, probably no, but you just shouldn't.
The reason you are facing this issue is because,
Glide is non Serializable data and you cannot serialize it. Since serialization is a process defined by Java and done on JVM, it doesn't understand Parcelable, hence fails with an error. This is also applicable even if you are just using them in the callback method.
But since you are developing a Library, you won't be able to control the usage of your Callback interface. So the solution would be avoid sending this object by Serializing it.
Instead, when usecase of your library starts (there must be some entry point to tigger the usecase), you need to request your to pass the reference directly to your method. You can then maintain the reference and execute callback when necessary. You need to implement some wrappers to achieve it. And communicate internally through LocalBroadcastReceiver
If the above approach is not possible, then only solution would be to ask user of your Library to register for LocalBroadcastReceive and deliver result through Broadcast.
Related
I'm trying to create a UI library that basically holds a lot of methods and actions I typically use when building a layout. One example of such a function is managing the fab clicks. I have one fab in the main activity, and I change its function, icon, and visibility based on which fragment is loaded. I'm currently doing everything with interfaces as you can see here. It all works fine, but the only issue is that I have to make sure other users use activities and fragments that extend my interface.
Example
protected void hideFab() {
capsuleActivity().getFab().hide();
}
protected CActivityCore capsuleActivity() {
if (!(getActivity() instanceof CActivityCore)) {
throw new RuntimeException(s(R.string.capsule_activity_context_error));
}
return ((CActivityCore) getActivity());
}
If they don't need the functions for one fragment, they have to override a lot of my methods to make it not do anything. My other option could be to use an EventBus, and to just send various events whenever something is called and resolve it accordingly. That way, it no longer matters if users use my classes or not; if they only want the functions in a few fragments, they can just extend my fragment for those fragments and not worry about the rest.
My question is, would this be a nice and viable way of doing things, or is this moreso overkill as to how EventBuses should be used?
Another function I'm considering is to have the activity send a callback to the current fragment and have it run only if the fragment extends a certain class.
Current implementation:
#SuppressWarnings("unchecked")
protected <T extends Fragment & CFragmentCore> void onCurrentFragment(Class<T> clazz, #NonNull Current<T> callback) {
Fragment current = getSupportFragmentManager().findFragmentById(getFragmentId());
if (clazz.isInstance(current.getClass())) {
callback.onMatch((T) current);
}
}
But I could also instead use an EventBus with an event containing the class and a callback function.
Using an EventBus is a design choice. It has advantages and disadvantages, which you can easily find on Google.
There is no such thing as an "overkill" if you are able to simplify your code.
Apart from that, an EventBus is probably a good idea in your scenario, as it provides an easy way to manage View-Controller communication.
For quite some time I've had troubles passing variables from one Activity to another, and I've usually had to resolve to some pretty ugly Static-class-hacks to make it work.
Generally something along the lines of a static method that I call with the type of the Activity, along with the variables the Activity requires. These gets stored in a static variable, and retrieved in the constructor of said activity.
Like I said, pretty ugly. And there's no such thing as "myActivity.StartActivity(new Activity);". All of the overloads for StartActivity takes either an Intent, or a typeof(MyOtherActivity).
So my question is, have I completely misunderstood the concept of Activities, or am I simply missing a completely obvious way to pass arguments to them?
#Edit: The reason I want to pass an actual reference to an object, instead of simply a copy of the object, is because I'm trying to pass a View Model from an overlying Activity, down to the new Activity. And of course any changes made to this view model, should be reflected on the parent activity, which will only be possible if the the two activy's viewmodels points to the same instance.
I'm writing the app using Xamarin.Android, but the code is nearly identical between C# and Java, so answers in either those languages is fine.
The problem is that Android can kill the process hosting your app at any time (if it is in the background). When the user then returns to your app, Android will create a new process to host your app and will recreate the Activity at the top of the stack. In order to do this, Android keeps a "serialized" version of the Intent so that it can recreate the Intent to pass it to the Activity. This is why all "extras" in an Intent need to be Parcelable or Serializable.
This is also why you cannot pass a reference to an object. When Android recreates the process, none of these objects will exist anymore.
Another point to consider is that different activities may run in different processes. Even activities from the same application may be in different processes (if the manifest specifies this). Since object references don't work across process boundaries, this is another reason why you cannot pass a reference to an object in an Intent.
You can also use The Application class to store objects globally and retrieve them:
using Android.Runtime;
namespace SomeName
{
[Application]
public class App : Application
{
public string Name { get; set;}
public App (IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
public override void OnCreate ()
{
base.OnCreate ();
Name = "";
}
}
}
And you can access the data with:
App application = (App)Application.Context;
application.Name = "something";
I choose to do this on the Application calss because this class is called on the App startup so you don't have to initiate it manually.
Keep in mind that variables which are scoped to the Application have their lifetime scoped to the application by extension.
This class will be Garbage Collected if the Android feels it is necessary so you have to modify the code to include this case also.
You can use SharedPreferences or a Database to save your variables in case they get deleted and retrieve them from the App class for faster results.
Don't be overly wasteful in how you use this approach though, as attaching too much information on this class it can lead to a degradation in performance. Only add information that you know will be needed by different parts of the application, and where the cost of retrieving that information exceeds the cost of storing it as an application variable.
Investigate which information you need to hold as application wide state, and what information can simply be retrieved straight from your database. There are cost implications for both and you need to ensure you get the balance right.
And don't forget to release resources as needed on OnStop and OnDestroy
I rarely use intents, i find this way better.
This might be a silly question, but I don't get why a Service cannot access an Activity when it is instantiated/running. I have a static attribute (actually a HashMap) in my Activity, so I needed to access its content and copy to my Service (then be independent to run on the background).
Whenever I try to access a class from my Service I get a NullPointer.
My code is basically this.
Within the Activity:
// check if its static attribute is empty
startService(new Intent(MyService.class.getName()));
Within MyService (which extends Service):
serviceList = MainActivity.list; // Here I suppose I should clone my list
I just didn't want to maybe send it through Parcel or Serialization, because I have custom objects in this HashMap.
Any solutions/explanation?
The answer was that I had my service running in a different process, so whenever I tried to access anything in my Activity, I got a NullPointerException.
Hope it helps someone in the future!
In my case, I have two classes MenuActivity, MinhaEscolaActivity.
There is a method in MenuActivity called quemSouEu, it is a non static method.
It also needs some properties defined on MinhaEscolaActivity's constructor method.
If I instanciate a new object of MinhaEscolaActivity on MenuActivity, these properties will be null, and i'll get NullPointerException.
Is there a way to use the method quemSouEu from MenuActivity class?
You shouldnt create an activity object by yourself. An activity is a main android component that is meant to be created by the system.
If you have some functions to be shared between activities, you should create another class, and instantiate an object in your activity.
If you also have data to share, you can think about some of the standar ways of sharing data, as you can read in this answer
What are the objects that you are creating on the second class constructor? you could start the activity and get the result, but youshould only do this if actually you need to show a new view or interact with the user in a diferent way, you shouldnt tell the system to run a new activity just for calling a function.
You can use intents to pass values between activities. You should never create an instance of activity class. Take a look at Raghav Sood's answer in the below post
Can i Create the object of a activity in other class?
Your storage options
http://developer.android.com/guide/topics/data/data-storage.html
Store the data and retrieve it when you need. Read the docs before choosing one.
You can pass data from MinhaEscolaActivity to MenuActivtiyusing intents and execute the method in MenuActivity it self
I am working with Android Fragments pretty extensively and one of the problems I am having is the lack of proper mechanism to pass complex objects to it.
In the android developers documentation for Fragments, they have a static method called newInstance called with some simple arguments which will be packaged into a Bundle and used inside the Fragment. However this method cannot be employed for passing complex objects.
Because I have been using the Fragments api a lot, it is becoming more and more important to have complex objects passed to it. One way to do this is by implementing the Parcelable interface, which I don't want to do for all the classes.
So, I thought I could do this :
Define an interface like this in the Fragment:
// Container Activity must implement this interface
public interface ReceiveDataInterface {
public MyClass getData(uniqueFragmentID);
}
Force the Activities using this fragment to implement the interface and call ReceiveDataInterface.getData(getArgument.getString(UNIQUEID))
In the Activity instantiate fragment using the newInstance method by passing a uniqueFragmentID and implement the ReceiveDataInterface which gives data based on uniqueFragmentID.
Is it a good idea to do this? if not, why? and how should I go about doing this?
Note:This is done in the same lines as OnArticleSelectedListener described in the documentation.
Any help is much appreciated.
We use this approach for any app we build for android. It works well. I havent tested every method of doing this, but i think this is among the best way.
Another approach would be to make the Fragment themselves independent. Why not write the code that fetches data from network/database in the Fragment itself?