20

I have searched the numerous questions that look like this one, but haven't found my answer in any of them.

I have an activity that has 3 tabs accessible through the action bar. I achieved this by adding 3 fragments that inflate a custom view I made extending the view class.

At the moment the database changes, I try to refresh the view in my tab by calling invalidate()/postinvalidate(), but this does not work. The same is true for calling onCreateView of the fragment just as many other options I considered.

When I go to another tab and go back, however, the change has been made and my view is updated as it should be.

How can I simulate the same thing that happens when changing to another tab? What does happen. I tried to look at the Fragment lifecycle (tried to call onCreateView()) to figure it out but it just doesn't want to refresh/redraw as it should.

The data is loaded properly, as the data is changed when I change to another tab.

I deleted some of the code as it is no longer relevant. I implemented Cursorloaders instead of my own Observer pattern to notify a change. This is my main activity right now.

The question is what should I do now if I want to redraw the view inside these fragments. If I apply fragmentObject.getView().invalidate() it does not work. I'm having the same problem as before, but now my Observer to notify a change in the database is properly implemented with loaders.

public class ArchitectureActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);     
    setContentView(R.layout.main);

    ActionBar actionbar = getActionBar();
    actionbar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

    ActionBar.Tab EditTab = actionbar.newTab().setText("Edit");
    ActionBar.Tab VisualizeTab = actionbar.newTab().setText("Visualize");
    ActionBar.Tab AnalyseTab = actionbar.newTab().setText("Analyse");

    Fragment editFragment = new EditFragment();
    Fragment visualizeFragment = new VisualizeFragment();
    Fragment analyseFragment = new AnalyseFragment();

    EditTab.setTabListener(new MyTabsListener(editFragment));
    VisualizeTab.setTabListener(new MyTabsListener(visualizeFragment));
    AnalyseTab.setTabListener(new MyTabsListener(analyseFragment));

    actionbar.addTab(EditTab);
    actionbar.addTab(VisualizeTab);
    actionbar.addTab(AnalyseTab);

    ArchitectureApplication architectureApplication = (ArchitectureApplication)getApplicationContext();
    architectureApplication.initialize();

    getLoaderManager().initLoader(0, null, this);
    getLoaderManager().initLoader(1, null, this);
}

public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    if (id == 0){
        return new CursorLoader(this, GraphProvider.NODE_URI , null, null, null, null);
    } else if (id == 1){
        return new CursorLoader(this, GraphProvider.ARC_URI , null, null, null, null);
    }
    return null;
}

public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    // Reloading of data, actually happens because when switching to another tab the new data shows up fine
    Log.e("Data", "loaded");
}

public void onLoaderReset(Loader<Cursor> loader) {
}
}
Alex Lockwood
  • 83,063
  • 39
  • 206
  • 250
J. Maes
  • 6,862
  • 5
  • 27
  • 33
  • Maybe you could do something with savedInstanceState? If you read the database tutorial provided with the Android API you will see a part that prevents action from being taken if the savedInstanceState changes or doesn't change. I can't remember exactly. Maybe you could cause the savedInstanceState to be reset which would cause a database update prompting a view refresh? Just an idea. – thomas.cloud Oct 07 '12 at 16:53
  • I noticed you aren't making any calls to `Fragment#getView()`... isn't the view returned by `getView()` the view that you are trying to manipulate? Perhaps the reason why the view isn't being re-drawn is because you aren't updating the correct view? – Alex Lockwood Oct 07 '12 at 21:06
  • In the current situation I do not call this. I tried calling editFragment.getView().invalidate(); in the observer, it gets called but it doesn't work. I have called the correct view in other ways, by passing it as a parameter to the method that also calls the addNode() method in the above ArchitectureData class – J. Maes Oct 07 '12 at 21:26

5 Answers5

23
  1. Don't try to call onCreateView() yourself... it's a lifecycle method and should be called only by the framework.

  2. Fragments are re-usable UI components. They have their own lifecycle, display their own view, and define their own behavior. You usually don't need to have your Activity mess around with the internal workings of a Fragment, as the Fragment's behavior should be self-contained and independent of any particular Activity.

    That said, I think the best solution is to have each of your Fragments implement the LoaderManager.LoaderCallbacks<D> interface. Each Fragment will initialize a Loader (i.e. a CursorLoader if you are using a ContentProvider backed by an SQLite database), and that Loader will be in charge of (1) loading the data on a background thread, and (2) listening for content changes that are made to the data source, and delivering new data to onLoadFinished() whenever a content change occurs.

    This solution is better than your current solution because it is entirely event-driven. You only need to refresh the view when data is delivered to onLoadFinished() (as opposed to having to manually check to see if the data source has been changed each time you click on a new tab).

  3. If you are lazy and just want a quick solution, you might be able to get away with refreshing the view in your Fragment's onResume() method too.

Alex Lockwood
  • 83,063
  • 39
  • 206
  • 250
  • Thank you for your answer and insights :). At the moment the priority is quick so I tried your third solution first but it didn't work. At this moment, the class communicating with my Contentprovider implements Observable, my view inside my fragment (or my fragment when I tried the onCreateView thing) implements Observer. In that way, the update method was called when my database was updated and I tried calling invalidate/onCreateView/onResume/... . I consider this to be event-driven too. I'll have a closer look at your second solution tomorrow, as I'm not familiar with loaders. – J. Maes Oct 07 '12 at 19:17
  • @J.Maes I've written a lot about `Loader`s beginning with [**this blog post**](http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html)... maybe it will help. The `CursorLoader` registers a `ContentObserver` on its `Cursor` automatically, so as long as your `ContentProvider` calls `Cursor#setNotificationUri(...)` in `query()` and `ContentResolver#notifyChange(...)` in when changes to the database are made, the `CursorLoader` will load new data and deliver it to `onLoadFinished()`. Implementing your own `ContentObserver` is probably complicating things... – Alex Lockwood Oct 07 '12 at 19:22
  • I understand that this implementation would be better, but could you say how this will make the difference between updating and not updating in comparison with the current situation? My current solution with my own observers is not really a solution as my my new data is not displayed (drawn actually). I gave a quick look at your blogpost and as I understand it now, I'll have to do something in onLoadFinished()/another method when the data has changed, but what do I have to put there to make sure my data gets drawn. edit/ The structure of your tutorial is btw one of the best I've ever seen g+1 – J. Maes Oct 07 '12 at 20:07
  • @J.Maes Thanks for the +1. :) Hmm... well, all of the `Loader` stuff I suggested was made under the assumption that your `View` was implemented correctly. It seems like that is the case from the fact that the changes are updated when you switch between tabs (I assume that this is because the `Fragment`s are being destroyed/re-created entirely when the fragment goes off/on screen). So given all of that, I'm willing to bet that the problem is that the way you are currently updating the view within the `Fragment` just isn't correct. This could be a problem with the `Fragment` or the `View`... – Alex Lockwood Oct 07 '12 at 20:24
  • @J.Maes could you post some of the code you currently have illustrating how you are notifying/setting the view's new data? – Alex Lockwood Oct 07 '12 at 20:25
  • I added some code, I'm sure my data gets loaded correctly, it just doesn't get drawn correctly when calling invalidate after it has loaded. Now that I take a closer look at my code, it gets dirtier and dirtier... Those loaders will make it cleaner – J. Maes Oct 07 '12 at 20:48
  • I've added some code after I implemented the loaders you suggested. The data gets updated properly but it still doesn't get drawn immediately if I call invalidate(). +1 for making my design better :) but the original problem is still there – J. Maes Oct 10 '12 at 07:29
  • @AlexLockwood Can you please help me on this :: http://stackoverflow.com/questions/20629665/strange-behaviour-of-actionbar-sherlock-searchview-above-api-level-11-after-call http://stackoverflow.com/questions/20606647/progress-dialog-not-aligned-in-the-actionbarsherlock-after-api-level-11 – AndroidLearner Dec 17 '13 at 11:35
4

I had a similar (although not identical) problem that I could solve in the following way:

In the fragment I added

public void invalidate() {
    myView.post(new Runnable() {
        @Override
        public void run() {
            myView.invalidate();
        }
    });
}

and I called this method from the activity when I wanted to refresh the view myView of the fragment. The use of post() ensures that the view is only invalidated when it is ready to be drawn.

Jörg Eisfeld
  • 1,299
  • 13
  • 7
3

I've found a workaround to refresh the view inside my fragment. I recreated (new CustomView) the view every time the database has been updated. After that I call setContentView(CustomView view). It looks more like a hack, but it works and nothing else that I tried does.

Although my problem was not actually solved, I gave the reward to Alex Lockwood. His advice on Loaders made my application better and it caused me to keep looking for a solution that I eventually found.

J. Maes
  • 6,862
  • 5
  • 27
  • 33
  • Sorry I couldn't give much advice... I've never actually implemented my own custom view before so I didn't have anything to give off the top of my head. I'm glad you found a workaround and that I helped make your app better though. :) – Alex Lockwood Oct 13 '12 at 02:29
  • @J.Maes I am having the same requirement, could you please tell you you created a CustomView and used it to setContentView. – Lalit Poptani Mar 21 '13 at 14:21
2

I had the same issue. My solution was detach fragment and attach it again.

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        Fragment f = getFragment(action);
        if(forceUpdate)
        {
            fragmentTransaction.detach(f);
            fragmentTransaction.attach(f);
        }
        fragmentTransaction.replace(R.id.mainFragment, f);
        fragmentTransaction.commit();
        currentAction = action;
mc.dev
  • 2,675
  • 3
  • 21
  • 27
  • Mik's 'fix' worked for me too....does anyone know a more elegant solution or can explain why this works? – Gene Myers Feb 02 '14 at 09:30
  • Does this go inside the Activity? – Si8 Oct 20 '16 at 10:17
  • Basically Yes. getSupportFragmentManager() is a method of FragmentActivity. However, you can put anywhere providing that you have FragmentManager instance in place. – mc.dev Oct 21 '16 at 21:09
2

The fastest solution working for me:

 @Override
    public void onPause() {
        super.onPause();
        if (isRemoving() && fragmentView != null) {
            ((ViewGroup) fragmentView).removeAllViews();
        }
    }
georgehardcore
  • 975
  • 14
  • 12