I have a Fragment that does some communication with the internet trough some helper Class that requires an interface to handle asynchronous callbacks like this:
SomeInternetCommunicator.getResource(theResourceToGet, retrieverInterfaceInstance);
The interface looks like this:
public interface SomeInternetCommunicator {
void onResourceRetrieveSuccess(SomeResource resource);
void onResourceRetrieveFailed(CommunicatorException e);
}
The problem is that, sometimes the calls take too long, the user already navigated elsewhere and the Fragment that did the call to getResource is not part of the currently running Activity anymore (not showing, not on backstack). This causes many problems because this orphan Fragment is still attempting to react to the callbacks even if it is not part of the Activity anymore. For example, getContext() returns null.
Now my workaround on the Fragment looks like this:
SomeInternetCommunicator flocktrackerTrackerRetrieverInterface = new SomeInternetCommunicator() {
#Override
public void onResourceRetrieveSuccess(SomeResource resource) {
if(isVisible()){
doRetrievalPostProcessing(resource);
}
}
#Override
void onResourceRetrieveFailed(CommunicatorException e) {
if(isVisible()){
doExceptionHandling();
}
}
};
Using the isVisible() works because this ensures that the fragment is still on the foreground, part of the Activity and ready to do the handling. This, however, doesn't help me to cover the case for when the Fragment is not visible, but still part of the current Activity, limiting my possibilities for loading the Fragment before showing it. (Activity is in the background, Fragment is on the Backstack, Fragment loaded by a ViewPager).
Is there a better way to ensure that the Fragment is still on the current Activity's scope before I do the postprocessing? Maybe checking isAdded()?
This question seems to explain a broader but related situation, but it has no answers.
Thank you!
there're two usual approaches to this case:
the best approach is to have a way to clear the interface instance from the SomeInternetCommunicator. Something like:
.
#Override public void onDestroyView(){
SomeInternetCommunicator.removeMe(this);
}
if option (1) is not possible because SomeInternetCommunicator is a poorly coded library, you force option 1 to be possible by making a separate class to implement the interface.
.
public class SomeInternetCommunicatorInterceptor implements SomeInternetCommunicatorInterface {
private SomeInternetCommunicatorInterface listener;
public void setListener(SomeInternetCommunicatorInterface listener){
this.listener=listener;
}
#Override
public void onResourceRetrieveSuccess(SomeResource resource){
if(listener != null){
listener.onResourceRetrieveSuccess(resource);
}
}
#Override
public void onResourceRetrieveFailed(CommunicatorException e){
if(listener != null){
listener.onResourceRetrieveFailed(e);
}
}
}
then on your fragment you use this new class as:
private SomeInternetCommunicatorInterceptor interceptor;
public void onCreateView(....){
... your code
interceptor = new SomeInternetCommunicatorInterceptor();
}
onStart(){
interceptor.setListener(this);
}
onStop(){
interceptor.setListener(null);
}
public void onDestroyView(){
interceptor = null;
super.onDestroyView();
}
VERY IMPORTANT:
if you make SomeInternetCommunicatorInterceptor an internal class of the fragment you HAVE TO make it as static class
Related
For Fragment(put data to activity)
m=(MainActivity)getActivity();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
Intent in=new Intent(getActivity(),MainActivity.class)
in.putExtra("test",test);
startActivty(in)
}
},10);
For Activity (get data from fragment )
{
String get_data=getIntent.getStringExtra("test");
}
//it will return always null...any body help me?
startActivty(in) will start the same activity.
Instead of this, you can make use of Interface. It's the easiest way to pass the data.
in your fragment, you can have an interface like,
SubmitData mSubmitData;
public interface SubmitData{
void DataListener(String s);
}
mSubmitData.DataListener("data to be sent");
In your activity, implement the SubmitData interface. It will make you override the DataListener method, where you can get the data.
public class MyActivity extends AppCompatActivity implements YourFragment.SubmitData{
#Override
public void DataListener(String s) {
// Data from the fragment
}
This questions has been asked and answered multiple times. You can find a valid reply here https://stackoverflow.com/a/9977370/5828132
Basically, it consists of creating an interface in the Fragment (for example) including a simple method. The Fragment has to declare a field of that type, and the Activity hosting the Fragment has to implement (implements) that interface. Both entities are usually connected using a explicit cast, in the onAttach() callback of the Fragment life-cycle, i.e.:
#Override
public void onAttach(Context context) {
super.onAttach(context);
// fragmentField_interfaceType = (interfaceType) context;
}
Hope it helps!
Sometimes, I have some headless fragments, which I need to run some initialization even before onCreate
For instance,
public class NetworkMonitorFragment extends Fragment {
public static NetworkMonitorFragment newInstance() {
return new NetworkMonitorFragment();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public void init() {
// This function shall be call even before onCreate.
}
}
NetworkMonitorFragment networkMonitorFragment = NetworkMonitorFragment.newInstance();
networkMonitorFragment.init();
I was wondering, is it a good practice, to have certain initialization inside Fragment constructor? Is there any drawback for doing so? The reason I'm asking, because I don't see many code example for doing so.
public class NetworkMonitorFragment extends Fragment {
public static NetworkMonitorFragment newInstance() {
return new NetworkMonitorFragment();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public NetworkMonitorFragment() {
init();
}
private void init() {
// This function shall be call even before onCreate.
}
}
NetworkMonitorFragment networkMonitorFragment = NetworkMonitorFragment.newInstance();
You certainly can, even other methods like Fragment.instantiate(Context context, String fname, Bundle args) calls newInstance() which calls default constructor. Although you must be aware of some things:
You should not do any stuff that is not independent of fragment's
state, lifecycle or Android's context
You should not do any stuff that takes up most of the 16ms UI
drawing window
You should not spawn new threads there
So while variable instantiation or some quick calculations based on external context, let's say, Date, for example, is perfectly fine, but decoding even a small bitmap either synchronously or asynchronously is a quick way to break things.
If execution and results of this function are not tied to fragment lifecycle and not dependent on parent activity then, I guess, it's just a matter of preference.
You can get more specific answers by describing what this function does.
If you want to do something before fragment created, do it in onAttach
(onAttach always calls before onCreate, look fragment lifecycle)
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
init();
...
}
If you want to sure view is created, do it in onViewCreated with same way.
I have an activity which may show some DialogFragments. This activity needs to get the response from the dialogs.
I use a listener. In activity:
ProgressMarkDialog dialog = new ProgressMarkDialog();
dialog.setOnProgressMarkSelected(new ProgressMarkDialog.OnProgressMarkSelected() {
#Override
public void onSelect(final int a) {
//some code..
}
});
In the dialog:
public void setOnProgressMarkSelected(OnProgressMarkSelected onProgressMarkSelected) {
this.onProgressMarkSelected = onProgressMarkSelected;
}
This code works fine until somehow the activity is destroyed, but the dialog still open. The program will crash with NullPointerException because the onProgressMarkSelected is null.
I can use
#Override
public void onAttach(final Activity activity) {
super.onAttach(activity);
onProgressMarkSelected = (OnProgressMarkSelected) activity;
}
and implement the interface in the activity.
But if I have few DialogFragments, that means I should implement few interface in the activity and the code will be very messy.
What is the Android best practice for this case?
In my opinion the best way is to stick to the standard positive/negative buttons and attaching DialogInterface.OnClickListener as shown in the http://developer.android.com/reference/android/app/DialogFragment.html
Look at it this way: standard Android user expects to see positive and/or negative button in the dialog. The Activity -if needed- should only be informed about the result (positive, negative input data) of the dialog. Any other input validation should be handled inside the DialogFragment
You could use inheritance with interfaces. Because on interfaces multiple inheritance is possible like this:
public interface A(){
void doA();
}
public interface B(){
void doB();
}
public interface BundleAB extends A, B {
void justAnotherMethod();
}
With this you can bundle your interfaces and use it like this:
public class MyClass implements BundleAB {
#Override
public void doA(){}
#Override
public void doB(){}
#Override
public void justAnotherMethod(){}
}
I have two Fragments in my Activity: fragment A with button X and fragment B with button Y.
How can I change button X's background image when I click button B? Is it possible?
From the documentation,
Because each fragment defines its own layout and its own behavior with its own lifecycle callbacks, you can include one fragment in multiple activities, so you should design for reuse and avoid directly manipulating one fragment from another fragment.
That being said, what you want to do is create event callbacks to the activity. A good way to do that is to define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary. This is the recommended way to share events between two separate Fragments--that is, sharing the event through the activity.
Check out the link above... it provides a couple nice examples. If you are still having trouble, let me know and maybe I can be more explicit.
Edit #1:
Let's say you click a button in fragment A and you want this to cause changes to a button in fragment B. Here's some sample code illustrating the concept:
The callback interface:
public interface OnButtonClickedListener {
public void onButtonClicked();
}
The activity:
public class SampleActivity extends Activity implements OnButtonClickedListener {
/* Implementation goes here */
public void onButtonClicked() {
// This method is called from fragment A, and when it is called,
// it will send information to fragment B. Remember to first
// check to see if fragment B is non-null.
/* Make call to a method in fragment B that will update its display */
}
}
Fragment A:
public class FragmentA extends Fragment {
OnButtonClickedListener mListener;
/* Implementation goes here */
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnButtonClickedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnButtonClickedListener ");
}
}
public void clickButton() {
// When the button is clicked, notify the activity.
// The activity will then pass the information to fragment
// B (if it has been created).
mListener.onButtonClicked();
}
}
Edit #2:
Now, you might be wondering, "Why would anyone ever go through all of this trouble? What's the point of creating a separate activity callback method when you could just have fragment A directly manipulate fragment B?"
The main reason you want to do this is to ensure that each fragment is designed as a modular and reusable activity component. This is especially important because a modular fragment allows you to change your fragment combinations for different screen sizes. When designing your application to support both tablets and handsets, you can reuse your fragments in different layout configurations to optimize the user experience based on the available screen space. For example, on a handset, it might be necessary to separate fragments to provide a single-pane UI when more than one cannot fit within the same activity. Making use of activity callbacks ensures that you will easily be able to reuse your fragments in situations where fragment B is not visible on the screen. For example, if you are on a handheld device and there is not enough room to display fragment B, then you can easily have your activity check to see if fragment B is currently being shown on the screen.
Sorry if this isn't clear... I'm finding it difficult to describe :P. Working your way through this tutorial might help... Activity callbacks make your life especially easier as a developer when you are working with interactive multi-pane layouts.
Base on Alex Lockwood's answer:
The activity:
public class SampleActivity extends Activity{
public interface OnButtonClickedListener {
public void onButtonClicked();
}
private OnButtonClickedListener onButtonClickedListener = null;
public OnButtonClickedListener getOnButtonClickedListener () {
return onButtonClickedListener
}
public void setOnButtonClickedListener (
OnButtonClickedListener onButtonClickedListener {
this.onButtonClickedListener = onButtonClickedListener;
}
}
Fragment A:
public class FragmentA extends Fragment {
private OnButtonClickedListener onButtonClickedListener = null;
private OnClickListener actionBarClickListener = new OnClickListener() {
#Override
public void onClick(View view) {
if (onButtonClickedListener == null){
onButtonClickedListener = ((SampleActivity) getActivity()).onButtonClickedListener ();
}
if (onButtonClickedListener != null) {
onButtonClickedListener
.onButtonClicked();
}
}
};
}
Fragment B:
public class FragmentB extends Fragment {
private OnButtonClickedListener onButtonClickedListener = new OnButtonClickedListener() {
#Override
public void onButtonClicked() {
Toast.makeText(getActivity(), "Button clicked", Toast.LENGTH_SHORT).show();
}
};
#Override
public void onResume() {
super.onResume();
SampleActivity sampleActivity = (SampleActivity) getActivity();
sampleActivity.setSearchBoxTextChangedListener(onButtonClickedListener);
}
}
Hope can help someone.
Setting the onClick attribute for a button in your layout, even your fragment's layout, will call the appropriate method on your Activity.
Your app can then send this signal from your Activity to fragment B.
I have an Android application that we are porting over to Honeycomb/Android 3.0 and we are using Fragments in our new interface.
I have search working via the widget as shown here.
The problem is, the widget doesn't pop up any more when using Fragments. So the question is how do I get search to be used with Fragments?
Or how can I replace this line to be used with Fragments?
getActivity().onSearchRequested();
I solved this problem using interface/callbacks.
In my MainActivity, I write a callback interface:
private SearchRequestedCallback mSearchRequestedCallback;
public void setSearchRequestedCallback(SearchRequestedCallback callback) {
mSearchRequestedCallback = callback;
}
public interface SearchRequestedCallback {
void onSearchRequested();
}
In my Fragment, I set the callback in the onStart() and unset it in the onStop():
#Override
public void onStart() {
super.onStart();
getActivity().setTitle(getResources().getString(R.string.app_name));
((MainActivity)getActivity()).setSearchRequestedCallback(new SearchRequestedCallback() {
#Override
public void onSearchRequested() {
addFilter();
}
});
}
#Override
public void onStop() {
((MainActivity)getActivity()).setSearchRequestedCallback(null);
super.onStop();
}
You can't. The SearchManager is implemented to work with Activitys, not Fragments. See this post for more information.