2

I'm building an App which extracts data from a SQLite local database.

I have an Activity, in which I use a ViewPager; in this ViewPager, there are 12 swipable Fragments.

I'd like to implement a "Reload" button which allows user to reload the actual Fragments, removing any changes caused by user modifications and resuming the original state of Fragment.

I tried to use notifyDataSetChanged() method on myAdapter (an extension of FragmentStatePagerAdapter) even if it should reload the whole pager: this method works (each Fragment is reloaded), but user modifications are not removed (for example, if I get an EditText and if I replace its text, pushing "Reload" button doesn't resume its original state).

I also tried to replace getItemPosition() method as suggested, but it doesn't solve the problem.

1) Activity:

public class MyActivity extends AppCompatActivity {
//code
    ViewPager pager;
    MyAdapter adapter;
    TabLayout tabLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //...
        //Pager settings
        adapter = new Adapter(getSupportFragmentManager(),this);
        pager = (ViewPager) findViewById(R.id.pager);
        pager.setAdapter(adapter);
        //TabLayout settings
        tabLayout = (TabLayout) findViewById(R.id.sliding_tabs);
        tabLayout.setupWithViewPager(pager);
        //...
    }
    //...
    //Method called after clicking on 'Reload' button
    public void reloadFragment() {
        adapter.notifyDataSetChanged();
    }
}

2) Adapter:

public class Adapter extends FragmentStatePagerAdapter {
    //...
    public Fragment getItem(int position) {
        Fragment f = null;
        switch (position) {
            case 0:
                f = new MyFragment01();
                break;
            //case from 0 to 11
        }
        return f;
    }
    //...
    @Override
    public int getItemPosition( Object object ) {
        return POSITION_NONE;
    }

3) MyFragment01 (1 of 12):

import android.support.v4.app.Fragment;
//...
public class MyFragment01 extends Fragment {
    PopulateEditText editText01;
    public MyFragment01() {
        //Empty constructor used to load from MyActivity
    }
    @Override
    public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        //...
        View myFragmentView = inflater.inflate(R.layout.layout_fragment_01, container, false);
        //...
        acquire(myFragmentView); //Acquire layout elements (EditTexts, Spinners, other)
        populate(); //Insert text on previous layout elements, based on SQLite data
        //...
        //'Reload' button
        ImageButton test = (ImageButton) myFragmentView.findViewById(R.id.testBtn);
        test.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ((MyActivity)getActivity()).reloadFragment();
             }
            });
        }
    }
}

EDIT #1 (about user yshahak's question)

3b) Details of method acquire(myFragmentView); from MyFragment01, which is called to inflate elements:

public void acquire(View myFragmentView) {
    editText01 = new PopulateEditText(R.id.editText01,myFragmentView);
    //...
}
public void populate() {
    //cursor is a cursor which extracts a record from SQLite Database
    //column is an attribute of this record
    editText01.initializeEditText(cursor,"column",true);
    //...
}

4) PopulateEditText class:

public class PopulateEditText {
    EditText editText;
    Activity activity;
    //Constructor
    public PopulateEditText(int elementFromLayout, View myFragmentView) {
        this.editText=(EditText) myFragmentView.findViewById(elementFromLayout);
        reset();
    }
    public void reset() {
        if(editText!=null)
            editText.setText("");
    }

    public void initializeEditText(Cursor c, String column, boolean enableEditText) {
        this.column=column;
        String s = c.getString(c.getColumnIndex(column));
        editText.setText(s);
        editText.setSelection(editText.getText().length());
        editText.setEnabled(enableEditText);
    }
}

EDIT #2
When i press 'Reload' button, these methods are called from MyFragment01 (checked by some breakpoints on debug):
1. onCreateView
2. acquire(myFragmentView)
3. populate()
And if I check my EditText's text, it's correctly reloaded at the start value, so it seems that "new Fragment" is reloaded, but not attached to ViewPager element.

Example:
- Start value: Text
- Modified value: Text 1234
- Hitting 'Reload', actual value: Text 1234
- getText().toString() from EditText: Text (expected result, not shown on actual view)

Flash
  • 1,134
  • 10
  • 12

2 Answers2

0

Lets try to look it slowly:

When you hit the "test" Button, what happens is the adpater bring all it Fragments and ask itself what to do with them. If you had use FragmentPageAdapter then because you define that it will throw them (with the "POSITION_NONE") it would had reload all Fragments by calling it getItem();

But because you use FragmentStatePagerAdapter extension it try to use it old Fragments so the old modification remain.

So basically I think that id you replace to FragmentPageAdapter it should work.

Keep in mind that this approach not really efficient to reload all Fragments just because you want to update one of them.

yshahak
  • 4,996
  • 1
  • 31
  • 37
  • First of all, thanks for your answer. I tried to replace **`FragmentPageStateAdapter`** with **`FragmentPageAdapter`**: it still "reloads" Fragments, but old modifications remain. Yeah you're right, when I'll be able to let this work, I will try to reload only the shown Fragment instead of all Fragments. – Flash Jul 01 '15 at 09:14
  • It seems that the modification is there just because the way you implemented onCreateView(), try to insert the full inflation to your question because obviously the Fragment reload – yshahak Jul 01 '15 at 09:32
  • I've just inserted the full inflation to my question (paragraph with "EDIT #1), I use `acquire(myFragmentView)` method to inflate and `populate()` to assign values from DB. Furthermore, I use a custom Class to manipulate EditTexts – Flash Jul 01 '15 at 09:55
  • This nice approach. Anyway did the data that user modify is temporary? You don't save it to the DataBase? If so it very strange. I would had put some breakPoint and start to debug and see if onCreateView is called after the hit on the reload and check exactly what happens in this inflation. – yshahak Jul 01 '15 at 10:03
  • I haven't implemented saving yet, it will be saved to the DataBase, I thought to do it as soon as I'll be able to fix this "Reload" problem.`onCreateView` is actually called when I hit reload. Also `acquire(myFragmentView)` and `populate()` methods are called, that's the weird thing. I've just added a breakpoint with a `getText().toString()` in order to see what editText01 contains and.. it shows the start value, not the modified one! And that's exactly what I would like to show (the start value). It seems that the Fragment reload is correct, but it's not attached to the adapter – Flash Jul 01 '15 at 10:20
  • You still use with FragmentPagerAdaoter? anyway you can try to force it ugly by reset the adapter itself with pager.setAdapter(new Adapter(getSupportFragmentManager(),this)); inside the reload code. – yshahak Jul 01 '15 at 10:31
  • and it low chance that it is the problem but maybe the adapter really show the right content but the pager use another adapter so you can try to replace adapter.notifyDataSetChanged(); with pager.getAdapter.notifyDataSetChanged(); – yshahak Jul 01 '15 at 10:40
  • Yes, I replaced `FragmentStatePagerAdapter` with `FragmentPagerAdapter` as suggested above. I just tried to force it ugly with `pager.setAdapter(new Adapter(getSupportFragmentManager(),this));`, Fragment seems to be reloaded, getText() keeps to return the start value but.. I still have the modified value shown on the EditText – Flash Jul 01 '15 at 10:45
  • According to your last comment, I also tried to recall adapter by `pager.getAdapter()`, but I've got the same result.. Correct value on debug, wrong value on my device's display – Flash Jul 01 '15 at 10:58
  • What happens if instead of reload the Fragment you just call populate(); inside the test Button? – yshahak Jul 01 '15 at 11:02
  • I've got NullPointerException, because inside `populate()` there are some references to Intent's extras (received from `MyActivity`), in particular, I have inside `populate()`: `String id = getActivity().getIntent().getExtras().getString("value")`. Null is returned by `getIntent()`, even if I tried to call it from `populate()` – Flash Jul 01 '15 at 11:17
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/82083/discussion-between-yshahak-and-flash). – yshahak Jul 01 '15 at 11:29
0

I found a solution for my problem. Inside Adapter, I added FragmentManager fm as variable in constructor:

FragmentManager fm;
public Adapter(FragmentManager fm, Activity activity) {
    super(fm);
    this.fm=fm;
    this.context=activity.getApplicationContext();
    this.activity=activity;
}

Inside MyActivity, I've just edited reloadFragment() method in this way, now that FragmentManager is available:

public void reloadFragment(int position) {
    Adapter adapter = (Adapter)pager.getAdapter();
    //This list contains a 'list' of Fragments available on ViewPager adapter. 
    List<Fragment> list = adapter.fm.getFragments();
    switch(position) {
        case 0:
            ((MyFragment01)list.get(position)).populate();
            break;
        //iterate for case from 0 to 11
    }
 }

Calling my populate() custom method on each Fragment, every EditText (and general elements) is correctly restored to its original value.

The argument position of reloadFragment(int position) is provided when I click on Reload button. I moved the method on a general class in order to call it from each fragment I want. Because of this, I needed to add view and activity in arguments:

public static void reloadButton(View myFragmentView, final Activity activity, final int position) {
    ImageButton test = (ImageButton) myFragmentView.findViewById(R.id.reloadBtn);
    test.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            ((MyActivity)activity).reloadFragment(position);
        }
    });
}

Maybe it's not the most efficient solution ever, if I'll find a better general solution, I'll publish it here

EDIT #1: I have to modify the way to implement the control, As yshahak told me, List is not a good way to solve my problems. That's why, when I open my activity, Fragment are not attached in order (from 0 to 11), but "on demand". So if I open my activity and if I swipe directly to 11th Fragment, that will be element number 3 instead of 11 (because my Activity cache only first 2 Fragment onCreate). I will search for another solution and I'll update here

Flash
  • 1,134
  • 10
  • 12