Android : Fragment and Observer - android

Main goal is to update Fragment info mainly from its own class.
Main activity:
public class MainActivity extends AppCompatActivity {
final Handler GUIHandler = new Handler();
final Runnable r = new Runnable()
{
public void run()
{
updateFragments();
GUIHandler.postDelayed(this, 1000);
}
};
#Override
protected void onPause() {
super.onPause();
GUIHandler.removeCallbacks(r);
}
#Override
protected void onResume() {
super.onResume();
GUIHandler.postDelayed(r, 600);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
...
mViewPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new PagerAdapter(getSupportFragmentManager(), tabLayout.getTabCount());
mViewPager.setAdapter(mPagerAdapter);
...
}
private void updateFragments() {
mPagerAdapter.updateFragments();
}
PagerAdapter:
public class PagerAdapter extends FragmentStatePagerAdapter {
int mNumOfTabs;
private Observable mObservers = new FragmentObserver();
public PagerAdapter(FragmentManager fm, int NumOfTabs) {
super(fm);
this.mNumOfTabs = NumOfTabs;
}
#Override
public Fragment getItem(int position) {
mObservers.deleteObservers(); // Clear existing observers.
switch (position) {
case 0:
FragmentWeather weatherTab = new FragmentWeather();
weatherTab.setActivity(mActivity);
if(weatherTab instanceof Observer)
mObservers.addObserver((Observer) weatherTab);
return weatherTab;
case 1:
FragmentMemo tab2 = new FragmentMemo();
return tab2;
case 2:
FragmentHardware tab3 = new FragmentHardware();
return tab3;
default:
return null;
}
}
public void updateFragments() {
mObservers.notifyObservers();
}
}
FragmentObserver
public class FragmentObserver extends Observable {
#Override
public void notifyObservers() {
setChanged(); // Set the changed flag to true, otherwise observers won't be notified.
super.notifyObservers();
Log.d("Observer", "Sending notification");
}
}
FragmentWeather:
public class FragmentWeather extends Fragment implements Observer {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
return layout;
}
public void setTemperatures(){
Log.d("Android", "setTemperatures is called");
}
#Override
public void update(Observable observable, Object data) {
setTemperatures();
}
}
Problem now is, that PagerAdapter::getItem() doesnt get called when Fragments are created at the start of application. That means WeatherFragment dont get associated with mObservers. If I swipe to the 3rd view and then swipe back, everything is working properly. How to restructurize this to make it working?

this line:
mObservers.deleteObservers(); // Clear existing observers.
is removing all the observers, but the method getItem gets called several times, that means only the last time it calls anything stays there. REMOVE this line.
Also, the following code is a very bad pattern and it will go wrong on several occasions:
case 0:
FragmentWeather weatherTab = new FragmentWeather();
weatherTab.setActivity(mActivity);
if(weatherTab instanceof Observer)
mObservers.addObserver((Observer) weatherTab);
return weatherTab;
that's because fragments get re-created by the system when necessary, so setActivity is pointless, so as is addObserver. The moment the system needs to destroy/recreate the fragments, you'll have a memory leak of those old fragments, the old activity, and the new ones won't have the activity and won't be on the observers.
The best situation here is to rely on the natural callbacks from the fragments. An example follows (ps.: that was typed by heart, I'm sure there might be some mistakes, but you'll get the idea)
public interface ObservableGetter{
public Observable getObservable();
}
public void MyFragment extends Fragment implements Observer {
#Override onAttach(Activity activity){
super.onAtttach(activity);
if(activity instanceof ObservableGetter){
((ObservableGetter)activity).getObservable().
addObserver(this);
}
}
#Overrude onDetach(){
Activity activity = getActivity();
if(activity instanceof ObservableGetter){
((ObservableGetter)activity).getObservable().
removeObserver(this);
}
super.onDetach();
}
}
then you can just make the activity implements ObservableGetter and have the Observable on it.
Then your adapter code will be just:
case 0:
return new FragmentWeather();
all the rest of the logic uses the regular callbacks.
I hope it helps.

Related

Recreate fragments in FragmentPagerAdapter

I'm creating application which has FragmentPagerAdapter with two pages.
The class for FragmentPagerAdapter looks like this
public static class AppSectionsPagerAdapter extends FragmentPagerAdapter {
public AppSectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment;
switch (i) {
case 0: fragment = new CurrentRateFragment(); break;
case 1: fragment = new HistoryFragment(); break;
default: fragment = new CurrentRateFragment(); break;
}
return fragment;
}
#Override
public int getCount() {
return 2;
}
}
I want that some changes on first page (for example change Spinner selected item) caused changes on second page.
As I've read about Fragment communication (https://developer.android.com/training/basics/fragments/communicating.html) and understand that fragments can communicate only through Activity.
For this case I've created public interface in my first page class fragment
public interface CurrencyListener {
public void onCurrencyChanged();
}
And implement it in my Activity.
Now I can call void onCurrencyChanged in my Activity from my first page fragment.
But the problem is:
How to recreate second page fragment in my FragmentPagerAdapter?
Fragment creation operation is heavy.
1. If you recreate fragment the all the views will be created and previous Fragment needs to be garbage collected. Therefore there will extra memory.
Therefore rather than recreating the FirstPageFragment just refresh the data based on the callback received on your currencyChanged() method.
In this case, fragment will be created once and data will be updated every time the currencyChanged() method is called.
HistoryFragment Code(Fragment to be refreshed)
public class HistoryFragment extends Fragment implements MyActivity.IUpdateData{
/**
* Other method goes here
*/
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//
if(getActivity() instanceof /**Your Activity**/){
((/**Your Activity**/)getActivity()).setOnUpdateListener(this);
}
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void updateData(Object object) {
//Refresh Data
}
}
Activity (Which will refresh data)
public class MyActivity extends Activity {
/**
* Other method goes here
*/
private IUpdateData dataUpdateListener;
public void setOnUpdateListener(IUpdateData listener){
dataUpdateListener = listener;
}
public void onCurrencyChanged(){
if(dataUpdateListener!=null){
dataUpdateListener.updateData(/**Update Data**/);
}
}
public interface IUpdateData{
void updateData(Object o);
}
}

How to pass arraylist from fragment to fragment using Activity in Android? [duplicate]

This question already has answers here:
Passing ArrayList from Fragment class to Activity
(2 answers)
Closed 5 years ago.
please give me good solution I need your help!.
I want to pass parameter using Interface class
from B Fragment to Activity and from Activity to C Fragment.
But Service asynctask I don't know C waiting for B ?
Please explain me this Fragments do what ?
EDIT:
public class FeedDetailActivity extends AppCompatActivity implements TabFragment_DetailFeed.ArrayPasser {
private Toolbar toolbar;
private TabLayout tabLayout;
private ViewPager viewPager;
String nameSurname;
int feedId;
public ArrayList<String> pathimg;
Bundle bundle;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_feeddetail);
// toolbar = (Toolbar) findViewById(R.id.toolbar);
// setSupportActionBar(toolbar);
// getSupportActionBar().setDisplayHomeAsUpEnabled(true);
nameSurname = getIntent().getStringExtra("nameSurname");
Intent i = getIntent();
feedId = i.getIntExtra("feedId",0); // 20 for default value
// feedId = getIntent().getStringExtra("feedId"); // Hata burda
viewPager = (ViewPager) findViewById(R.id.pager);
setupViewPager(viewPager);
tabLayout = (TabLayout) findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(viewPager);
/* TabFragment_DetailComment EditTextinden otomatik klavye açtırmasını kapatmasını sağlıyor. */
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
);
}
public String getNameSurname() {
return nameSurname;
}
public int getFeedId() {
return feedId;
}
private void setupViewPager(ViewPager viewPager) {
FeedDetailActivity.ViewPagerAdapter adapter = new FeedDetailActivity.ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(new TabFragment_DetailFeed(), "DETAIL");
adapter.addFragment(new TabFragment_DetailComment(), "COMMENT");
adapter.addFragment(new TabFragment_DetailImage(), "IMAGE");
adapter.addFragment(new TabFragment_DetailSurvey(), "SURVEY");
viewPager.setAdapter(adapter);
}
#Override
public void sendArray(ArrayList<String> strings) {
TabFragment_DetailImage frag = (TabFragment_DetailImage)
getSupportFragmentManager().findFragmentById();
frag.method(strings);
}
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
}
You should try to use EventBus. With it you can register receivers in your fragments and post events from any part of code.
Here is a little example for your fragments:
In C fragment override onResume() and onPause() like this
#Override
public void onResume() {
super.onResume();
EventBus.getDefault().register(this);
}
#Override
public void onPause() {
super.onPause();
EventBus.getDefault().unregister(this);
}
And also subscribe to your event (you need to create class for it)
#Subscribe(threadMode = ThreadMode.MAIN)
public void onResult(OnResultEvent event) {
//do stuff in event
}
Then in B fragment do like this
public void doStuff(){
//do stuff here
EventBus.getDefault().post(new OnResultEvent(result));
}
You Activity implements your interface(ArrayPasser) in this case which is defined in FragmentA
public class YourActivity implements FragmentA.ArrayPasser{
#Override
public void sendArray(ArrayList<String> strings){
// Get instance of Fragment B using FragmentManager
FraB frag = (FragB)
getSupportFragmentManager().findFragmentById(R.id.fragment_b);
frag.someMethod(strings); //passing arraylist to Fragment B from
Activity
}
}
// Fragment A defines an Interface, and calls the method when needed
public class FragA extends Fragment{
ArrayPasser mCallback; //interface reference
public interface ArrayPasser{
public void sendArray(ArrayList<String> strings);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (ArrayPasser) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement ArrayPasser");
}
}
public void someMethod(ArrayList<String>strings){
mCallback.sendArray(strings); //passing array list to fragment
}
#Override
public void onDetach() {
mCallback = null; // => avoid leaking
super.onDetach();
}
}
// Fragment B has a public method to do something with the Array
public class FragB extends Fragment{
public void method(ArrayList<String> strings){
// Here you have it
}
}

Fragment not added (inconsistent crash)

In my MainActivity, I have:
#Override
protected void onResume() {
super.onResume();
checkForCrashes();
checkForTutorial();
checkForUpdates();
setStore();
setup();
}
In setup(), I call initializeTabs() in a callback:
protected void setup() {
final Store s = getStore();
setBackground();
if (s == null) {
unauthorizedHandler();
return;
}
final Context mainActivity = this;
fragments = getTabFragments();
StoresController.getStoreDetails(s, mainActivity, new Callback<StoreDetailDecorator>() {
#Override
public void success(StoreDetailDecorator storeDetailDecorator, Response response) {
s.prettyName = storeDetailDecorator.store.pretty_name;
s.save();
Log.v(TAG, s.prettyName);
TextView toolbar_label = (TextView)findViewById(R.id.toolbar_label);
toolbar_label.setText(MainActivity.getTruncatedMenuName(s.name()));
SummaryTab t1 = (SummaryTab)fragments.get(0);
t1.notifier = (SummaryTabLoadingNotifier)mainActivity;
initializeTabs(s, fragments);
t1.populateReport();
}
}
}
public void initializeTabs(Store s, List<Fragment> fragments ) {
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager(), getTabTitles(), fragments);
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setOffscreenPageLimit(2);
pager.setAdapter(adapter);
}
That last line is crashing for some customers.
java.lang.IllegalStateException: Fragment InfoTab{1bcb80f} is not currently in the FragmentManager
But not every time. Why would this happen sometimes? Something to do with the fragment not being connected? I read some things about checking isAdded() on a fragment, but that's for checking if a fragment is added to an Activity. I'm using a FragmentStatePagerAdapter :
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
private CharSequence[] titles;
private List<Fragment> fragmentList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager fragmentManager, CharSequence titles[], List<Fragment> fragmentList) {
super(fragmentManager);
this.titles = titles;
this.fragmentList = fragmentList;
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
return fragmentList.get(position);
}
#Override
public CharSequence getPageTitle(int position) { return titles[position]; }
#Override
public int getCount() { return fragmentList.size(); }
#Override
public Parcelable saveState() {
return null;
}
}
Could saveState(){ return null;} be it? This is there, as I understand things, to ensure the view is refreshed every time.
onResume() is called every time your activity returns to foreground.
If one of your users, for example, presses the home button and returns to your app then getStoreDetails() will be called again and you'll create a new adapter (with the same old fragment list) when the result arrives.
You should instantiate your fragments, create an adapter and populate your viewpager with the adapter only once (onCreate() would be the spot).
When new data arrives from getStoreDetails() just update the already added fragments with the new data.
This is because you are returning null in saveState(). You shouldn't try to override this behavior.
But even if you want to do it for some reason, you have to override public void restoreState(Parcelable state, ClassLoader loader) as well to avoid crash, because that method is relying on data saved in saveState().
But I would suggest you to not override it at all, and instead explain what you wanted to achieve there.

Using LocalBroadcastManager to communicate from Fragment to Activity

EDIT: This question was created as part of one of my first Android projects when I was just starting out with Android application development. I'm keeping this for historical reasons, but you should consider using EventBus or RxJava instead. This is a gigantic mess.
Please DO NOT CONSIDER using this. Thank you.
In fact, if you want something cool that solves the problem of using a single activity with multiple "fragments", then use flowless with custom viewgroups.
I have implemented a way to initiate the creation of Fragments, from Fragments using a broadcast intent through the LocalBroadcastManager to tell the Activity what Fragment to instantiate.
I know this is a terribly long amount of code, but I'm not asking for debugging, it works perfectly as I intended - the data is received, the creation can be parametrized by Bundles, and Fragments don't directly instantiate other Fragments.
public abstract class FragmentCreator implements Parcelable
{
public static String fragmentCreatorKey = "fragmentCreator";
public static String fragmentCreationBroadcastMessage = "fragment-creation";
public static String fragmentDialogCreationBroadcastMessage = "fragment-dialog-creation";
protected Bundle arguments;
protected Boolean hasBundle;
public FragmentCreator(Bundle arguments, boolean hasBundle)
{
this.arguments = arguments;
this.hasBundle = hasBundle;
}
protected FragmentCreator(Parcel in)
{
hasBundle = (Boolean) in.readSerializable();
if (hasBundle == true && arguments == null)
{
arguments = in.readBundle();
}
}
public Fragment createFragment()
{
Fragment fragment = instantiateFragment();
if (arguments != null)
{
fragment.setArguments(arguments);
}
return fragment;
}
protected abstract Fragment instantiateFragment();
#Override
public int describeContents()
{
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags)
{
dest.writeSerializable(hasBundle);
if (arguments != null)
{
arguments.writeToParcel(dest, 0);
}
}
public void sendFragmentCreationMessage(Context context)
{
Intent intent = new Intent(FragmentCreator.fragmentCreationBroadcastMessage);
intent.putExtra(FragmentCreator.fragmentCreatorKey, this);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
public void sendDialogFragmentCreationMessage(Context context)
{
Intent intent = new Intent(FragmentCreator.fragmentDialogCreationBroadcastMessage);
intent.putExtra(FragmentCreator.fragmentCreatorKey, this);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
}
This way, a Fragment that is created looks like this:
public class TemplateFragment extends Fragment implements GetActionBarTitle, View.OnClickListener
{
private int titleId;
public TemplateFragment()
{
titleId = R.string.app_name;
}
#Override
public int getActionBarTitleId()
{
return titleId;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_template, container, false);
return rootView;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
}
#Override
public void onClick(View v)
{
}
public static class Creator extends FragmentCreator
{
public Creator()
{
super(null, false);
}
public Creator(Bundle bundle)
{
super(bundle, true);
}
protected Creator(Parcel in)
{
super(in);
}
#Override
protected Fragment instantiateFragment()
{
return new TemplateFragment();
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<TemplateFragment.Creator> CREATOR = new Parcelable.Creator<TemplateFragment.Creator>()
{
#Override
public TemplateFragment.Creator createFromParcel(Parcel in)
{
return new TemplateFragment.Creator(in);
}
#Override
public TemplateFragment.Creator[] newArray(int size)
{
return new TemplateFragment.Creator[size];
}
};
}
}
The initial container activity that can process the messages looks like this:
Intent intent = new Intent();
intent.setClass(this.getActivity(), ContainerActivity.class);
intent.putExtra(FragmentCreator.fragmentCreatorKey,
new TemplateFragment.Creator());
startActivity(intent);
And the Fragments "instantiate other Fragments" like this:
Bundle bundle = new Bundle();
bundle.putParcelable("argument", data);
TemplateFragment.Creator creator = new TemplateFragment.Creator(bundle);
creator.sendFragmentCreationMessage(getActivity());
And the Container Activity receives the instantiation request:
public class ContainerActivity extends ActionBarActivity implements SetFragment, ShowDialog
{
private BroadcastReceiver mFragmentCreationMessageReceiver = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
setFragment((FragmentCreator) intent.getParcelableExtra(FragmentCreator.fragmentCreatorKey));
}
};
private BroadcastReceiver mFragmentDialogCreationMessageReceiver = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
showDialog((FragmentCreator) intent.getParcelableExtra(FragmentCreator.fragmentCreatorKey));
}
};
#Override
public void onCreate(Bundle saveInstanceState)
{
super.onCreate(saveInstanceState);
this.setContentView(R.layout.activity_container);
getActionBar().setDisplayHomeAsUpEnabled(true);
if (saveInstanceState == null)
{
Fragment fragment = ((FragmentCreator) getIntent().getParcelableExtra(
FragmentCreator.fragmentCreatorKey)).createFragment();
if (fragment != null)
{
replaceFragment(fragment);
}
}
else
{
this.getActionBar()
.setTitle(
((GetActionBarTitle) (this.getSupportFragmentManager()
.findFragmentById(R.id.activity_container_container)))
.getActionBarTitleId());
}
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener()
{
public void onBackStackChanged()
{
int backCount = getSupportFragmentManager().getBackStackEntryCount();
if (backCount == 0)
{
finish();
}
}
});
}
#Override
protected void onResume()
{
LocalBroadcastManager.getInstance(this).registerReceiver(mFragmentCreationMessageReceiver,
new IntentFilter(FragmentCreator.fragmentCreationBroadcastMessage));
LocalBroadcastManager.getInstance(this).registerReceiver(mFragmentDialogCreationMessageReceiver,
new IntentFilter(FragmentCreator.fragmentDialogCreationBroadcastMessage));
super.onResume();
}
#Override
protected void onPause()
{
super.onPause();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mFragmentCreationMessageReceiver);
LocalBroadcastManager.getInstance(this).unregisterReceiver(
mFragmentDialogCreationMessageReceiver);
}
#Override
public void setFragment(FragmentCreator fragmentCreator)
{
Fragment fragment = fragmentCreator.createFragment();
replaceFragment(fragment);
}
public void replaceFragment(Fragment fragment)
{
if (fragment != null)
{
this.setTitle(((GetActionBarTitle) fragment).getActionBarTitleId());
getSupportFragmentManager().beginTransaction()
.replace(R.id.activity_container_container, fragment).addToBackStack(null).commit();
}
}
#Override
public void showDialog(FragmentCreator fragmentCreator)
{
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fragmentCreator.createFragment();
if (fragment instanceof DialogFragment)
{
DialogFragment df = (DialogFragment) fragment;
df.show(fm, "dialog");
}
else
{
Log.e(this.getClass().getSimpleName(), "showDialog() called with non-dialog parameter!");
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
if (item.getItemId() == android.R.id.home)
{
this.onBackPressed();
}
return super.onOptionsItemSelected(item);
}
}
My question is, is this actually a good idea, or is this a terrible case of "over-engineering" (creating a Factory for each Fragment and sending it to the Activity in the form of a local broadcast, rather than just casting the Activity of the most possible holder activity's interface and calling the function like that)?
My goal was that this way, I can use the same Activity for holding "branch" fragments, so that I don't need to make one for each menu point. Rather than just re-use the same activity, and divide all logic into fragments. (Currently it doesn't support orientation-based layout organization, I see that downside - and also that this way each Fragment needs to hold a static creator, which is extra 'boilerplate code').
If you know the answer why I shouldn't be using the local broadcast manager for this, I'll be happy to hear the response. I think it's pretty neat, but there's a chance it's just overcomplicating something simple.
You can use Interface for it so main objective of Fragment re-usability is maintained. You can implement communication between Activity-Fragment OR Fragment-Fragment via using following :
I am asuming that your moto is Fragment to communicate with its Activity and other Fragments.
If this is the case please go throught it.
To allow a Fragment to communicate up to its Activity, you can define an interface in the Fragment class and implement it within the Activity. The Fragment captures the interface implementation during its onAttach() lifecycle method and can then call the Interface methods in order to communicate with the Activity.
Example :
# In fragment
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCallback = (OnHeadlineSelectedListener) activity;
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
mCallback.onArticleSelected(position);
}
}
# In Activity
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{
public void onArticleSelected(int position) {
// Do something here
}
}
Link: http://developer.android.com/training/basics/fragments/communicating.html

Android. Fragment getActivity() sometimes returns null

In developer console error reports sometimes I see reports with NPE issue. I do not understand what is wrong with my code. On emulator and my device application works good without forcecloses, however some users get NullPointerException in fragment class when the getActivity() method is called.
Activity
pulic class MyActivity extends FragmentActivity{
private ViewPager pager;
private TitlePageIndicator indicator;
private TabsAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
pager = (ViewPager) findViewById(R.id.pager);
indicator = (TitlePageIndicator) findViewById(R.id.indicator);
adapter = new TabsAdapter(getSupportFragmentManager(), false);
adapter.addFragment(new FirstFragment());
adapter.addFragment(new SecondFragment());
indicator.notifyDataSetChanged();
adapter.notifyDataSetChanged();
// push first task
FirstTask firstTask = new FirstTask(MyActivity.this);
// set first fragment as listener
firstTask.setTaskListener((TaskListener) adapter.getItem(0));
firstTask.execute();
}
indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
Fragment currentFragment = adapter.getItem(position);
((Taskable) currentFragment).executeTask();
}
#Override
public void onPageScrolled(int i, float v, int i1) {}
#Override
public void onPageScrollStateChanged(int i) {}
});
}
AsyncTask class
public class FirstTask extends AsyncTask{
private TaskListener taskListener;
...
#Override
protected void onPostExecute(T result) {
...
taskListener.onTaskComplete(result);
}
}
Fragment class
public class FirstFragment extends Fragment immplements Taskable, TaskListener{
public FirstFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.first_view, container, false);
}
#Override
public void executeTask() {
FirstTask firstTask = new FirstTask(MyActivity.this);
firstTask.setTaskListener(this);
firstTask.execute();
}
#Override
public void onTaskComplete(T result) {
// NPE is here
Resources res = getActivity().getResources();
...
}
}
Maybe this error happens when applications resumed from background. In this case how I should handle this situation properly?
It seems that I found a solution to my problem.
Very good explanations are given here and here.
Here is my example:
pulic class MyActivity extends FragmentActivity{
private ViewPager pager;
private TitlePageIndicator indicator;
private TabsAdapter adapter;
private Bundle savedInstanceState;
#Override
public void onCreate(Bundle savedInstanceState) {
....
this.savedInstanceState = savedInstanceState;
pager = (ViewPager) findViewById(R.id.pager);;
indicator = (TitlePageIndicator) findViewById(R.id.indicator);
adapter = new TabsAdapter(getSupportFragmentManager(), false);
if (savedInstanceState == null){
adapter.addFragment(new FirstFragment());
adapter.addFragment(new SecondFragment());
}else{
Integer count = savedInstanceState.getInt("tabsCount");
String[] titles = savedInstanceState.getStringArray("titles");
for (int i = 0; i < count; i++){
adapter.addFragment(getFragment(i), titles[i]);
}
}
indicator.notifyDataSetChanged();
adapter.notifyDataSetChanged();
// push first task
FirstTask firstTask = new FirstTask(MyActivity.this);
// set first fragment as listener
firstTask.setTaskListener((TaskListener) getFragment(0));
firstTask.execute();
}
private Fragment getFragment(int position){
return savedInstanceState == null ? adapter.getItem(position) : getSupportFragmentManager().findFragmentByTag(getFragmentTag(position));
}
private String getFragmentTag(int position) {
return "android:switcher:" + R.id.pager + ":" + position;
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("tabsCount", adapter.getCount());
outState.putStringArray("titles", adapter.getTitles().toArray(new String[0]));
}
indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
Fragment currentFragment = adapter.getItem(position);
((Taskable) currentFragment).executeTask();
}
#Override
public void onPageScrolled(int i, float v, int i1) {}
#Override
public void onPageScrollStateChanged(int i) {}
});
The main idea in this code is that, while running your application normally, you create new fragments and pass them to the adapter. When you are resuming your application fragment manager already has this fragment's instance and you need to get it from fragment manager and pass it to the adapter.
UPDATE
Also, it is a good practice when using fragments to check isAdded before getActivity() is called. This helps avoid a null pointer exception when the fragment is detached from the activity. For example, an activity could contain a fragment that pushes an async task. When the task is finished, the onTaskComplete listener is called.
#Override
public void onTaskComplete(List<Feed> result) {
progress.setVisibility(View.GONE);
progress.setIndeterminate(false);
list.setVisibility(View.VISIBLE);
if (isAdded()) {
adapter = new FeedAdapter(getActivity(), R.layout.feed_item, result);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
}
If we open the fragment, push a task, and then quickly press back to return to a previous activity, when the task is finished, it will try to access the activity in onPostExecute() by calling the getActivity() method. If the activity is already detached and this check is not there:
if (isAdded())
then the application crashes.
Ok, I know that this question is actually solved but I decided to share my solution for this. I've created abstract parent class for my Fragment:
public abstract class ABaseFragment extends Fragment{
protected IActivityEnabledListener aeListener;
protected interface IActivityEnabledListener{
void onActivityEnabled(FragmentActivity activity);
}
protected void getAvailableActivity(IActivityEnabledListener listener){
if (getActivity() == null){
aeListener = listener;
} else {
listener.onActivityEnabled(getActivity());
}
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (aeListener != null){
aeListener.onActivityEnabled((FragmentActivity) activity);
aeListener = null;
}
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (aeListener != null){
aeListener.onActivityEnabled((FragmentActivity) context);
aeListener = null;
}
}
}
As you can see, I've added a listener so, whenever I'll need to get Fragments Activity instead of standard getActivity(), I'll need to call
getAvailableActivity(new IActivityEnabledListener() {
#Override
public void onActivityEnabled(FragmentActivity activity) {
// Do manipulations with your activity
}
});
The best to get rid of this is to keep activity reference when onAttach is called and use the activity reference wherever needed, for e.g.
#Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;
}
#Override
public void onDetach() {
super.onDetach();
mContext = null;
}
Edited, since onAttach(Activity) is depreciated & now onAttach(Context) is being used
Don't call methods within the Fragment that require getActivity() until onStart in the parent Activity.
private MyFragment myFragment;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
myFragment = new MyFragment();
ft.add(android.R.id.content, youtubeListFragment).commit();
//Other init calls
//...
}
#Override
public void onStart()
{
super.onStart();
//Call your Fragment functions that uses getActivity()
myFragment.onPageSelected();
}
I've been battling this kind of problem for a while, and I think I've come up with a reliable solution.
It's pretty difficult to know for sure that this.getActivity() isn't going to return null for a Fragment, especially if you're dealing with any kind of network behaviour which gives your code ample time to withdraw Activity references.
In the solution below, I declare a small management class called the ActivityBuffer. Essentially, this class deals with maintaining a reliable reference to an owning Activity, and promising to execute Runnables within a valid Activity context whenever there's a valid reference available. The Runnables are scheduled for execution on the UI Thread immediately if the Context is available, otherwise execution is deferred until that Context is ready.
/** A class which maintains a list of transactions to occur when Context becomes available. */
public final class ActivityBuffer {
/** A class which defines operations to execute once there's an available Context. */
public interface IRunnable {
/** Executes when there's an available Context. Ideally, will it operate immediately. */
void run(final Activity pActivity);
}
/* Member Variables. */
private Activity mActivity;
private final List<IRunnable> mRunnables;
/** Constructor. */
public ActivityBuffer() {
// Initialize Member Variables.
this.mActivity = null;
this.mRunnables = new ArrayList<IRunnable>();
}
/** Executes the Runnable if there's an available Context. Otherwise, defers execution until it becomes available. */
public final void safely(final IRunnable pRunnable) {
// Synchronize along the current instance.
synchronized(this) {
// Do we have a context available?
if(this.isContextAvailable()) {
// Fetch the Activity.
final Activity lActivity = this.getActivity();
// Execute the Runnable along the Activity.
lActivity.runOnUiThread(new Runnable() { #Override public final void run() { pRunnable.run(lActivity); } });
}
else {
// Buffer the Runnable so that it's ready to receive a valid reference.
this.getRunnables().add(pRunnable);
}
}
}
/** Called to inform the ActivityBuffer that there's an available Activity reference. */
public final void onContextGained(final Activity pActivity) {
// Synchronize along ourself.
synchronized(this) {
// Update the Activity reference.
this.setActivity(pActivity);
// Are there any Runnables awaiting execution?
if(!this.getRunnables().isEmpty()) {
// Iterate the Runnables.
for(final IRunnable lRunnable : this.getRunnables()) {
// Execute the Runnable on the UI Thread.
pActivity.runOnUiThread(new Runnable() { #Override public final void run() {
// Execute the Runnable.
lRunnable.run(pActivity);
} });
}
// Empty the Runnables.
this.getRunnables().clear();
}
}
}
/** Called to inform the ActivityBuffer that the Context has been lost. */
public final void onContextLost() {
// Synchronize along ourself.
synchronized(this) {
// Remove the Context reference.
this.setActivity(null);
}
}
/** Defines whether there's a safe Context available for the ActivityBuffer. */
public final boolean isContextAvailable() {
// Synchronize upon ourself.
synchronized(this) {
// Return the state of the Activity reference.
return (this.getActivity() != null);
}
}
/* Getters and Setters. */
private final void setActivity(final Activity pActivity) {
this.mActivity = pActivity;
}
private final Activity getActivity() {
return this.mActivity;
}
private final List<IRunnable> getRunnables() {
return this.mRunnables;
}
}
In terms of its implementation, we must take care to apply the life cycle methods to coincide with the behaviour described above by Pawan M:
public class BaseFragment extends Fragment {
/* Member Variables. */
private ActivityBuffer mActivityBuffer;
public BaseFragment() {
// Implement the Parent.
super();
// Allocate the ActivityBuffer.
this.mActivityBuffer = new ActivityBuffer();
}
#Override
public final void onAttach(final Context pContext) {
// Handle as usual.
super.onAttach(pContext);
// Is the Context an Activity?
if(pContext instanceof Activity) {
// Cast Accordingly.
final Activity lActivity = (Activity)pContext;
// Inform the ActivityBuffer.
this.getActivityBuffer().onContextGained(lActivity);
}
}
#Deprecated #Override
public final void onAttach(final Activity pActivity) {
// Handle as usual.
super.onAttach(pActivity);
// Inform the ActivityBuffer.
this.getActivityBuffer().onContextGained(pActivity);
}
#Override
public final void onDetach() {
// Handle as usual.
super.onDetach();
// Inform the ActivityBuffer.
this.getActivityBuffer().onContextLost();
}
/* Getters. */
public final ActivityBuffer getActivityBuffer() {
return this.mActivityBuffer;
}
}
Finally, in any areas within your Fragment that extends BaseFragment that you're untrustworthy about a call to getActivity(), simply make a call to this.getActivityBuffer().safely(...) and declare an ActivityBuffer.IRunnable for the task!
The contents of your void run(final Activity pActivity) are then guaranteed to execute along the UI Thread.
The ActivityBuffer can then be used as follows:
this.getActivityBuffer().safely(
new ActivityBuffer.IRunnable() {
#Override public final void run(final Activity pActivity) {
// Do something with guaranteed Context.
}
}
);
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// run the code making use of getActivity() from here
}
I know this is a old question but i think i must provide my answer to it because my problem was not solved by others.
first of all : i was dynamically adding fragments using fragmentTransactions.
Second: my fragments were modified using AsyncTasks (DB queries on a server).
Third: my fragment was not instantiated at activity start
Fourth: i used a custom fragment instantiation "create or load it" in order to get the fragment variable.
Fourth: activity was recreated because of orientation change
The problem was that i wanted to "remove" the fragment because of the query answer, but the fragment was incorrectly created just before. I don't know why, probably because of the "commit" be done later, the fragment was not added yet when it was time to remove it. Therefore getActivity() was returning null.
Solution :
1)I had to check that i was correctly trying to find the first instance of the fragment before creating a new one
2)I had to put serRetainInstance(true) on that fragment in order to keep it through orientation change (no backstack needed therefore no problem)
3)Instead of "recreating or getting old fragment" just before "remove it", I directly put the fragment at activity start.
Instantiating it at activity start instead of "loading" (or instantiating) the fragment variable before removing it prevented getActivity problems.
In Kotlin you can try this way to handle getActivity() null condition.
activity?.let { // activity == getActivity() in java
//your code here
}
It will check activity is null or not and if not null then execute inner code.

Categories

Resources