Search code examples
androidandroid-fragmentsobservers

Android : Fragment and Observer


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?


Solution

  • 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.