androiduser-interfacefragmentstatepageradapter

What is wrong with how I am using a FragmentStatePagerAdapter?


I have an Activity using a FragmentStatePagerAdapter. If I launch another activity that changes some data involved with what is displayed, the view is not updated. If the adapter is handling tabs, each to show different aspects of the same object via Fragments, if an object attribute is changed by an activity launched from a page handled by the adapter, and the adapter notifyDataSetChanged is called in onActivityResult, the data in the tab view is not getting updated, as I expect it should be. I cannot figure out why.

In the activity class:

public class EventDetailActivity extends AppCompatActivity
{
    public ViewPager viewPager;
    public PagerAdapter adapter; // This extends FragmentStatePagerAdapter
    public TabLayout tabLayout;
    public Event currentEvent; // ****** Contains the data to display in tabs

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView (R.layout.activity_event_detail);

        currentEvent = (Event)getIntent().getSerializableExtra(Event.EVENT_KEY); // ***** The object on display was serialized to pass in the intent.
        // Serializing it in the initial intent is not a problem, because it is saved in the database within this activity,
        // and the calling activity gets the update via the database.
        tabLayout = (TabLayout) findViewById (R.id.tab_layout);
        tabLayout.addTab(tabLayout.newTab().
            setText(getResources().getString(R.string.details)));
        ... Add other tabs. ...
        tabLayout.setTabGravity (TabLayout.GRAVITY_FILL);
        viewPager = (ViewPager) findViewById (R.id.pager);
        adapter = new PagerAdapter(currentEvent, getSupportFragmentManager(), tabLayout.getTabCount());
        viewPager.setAdapter (adapter);
        viewPager.addOnPageChangeListener (new TabLayout.TabLayoutOnPageChangeListener (tabLayout));
        tabLayout.setOnTabSelectedListener (new TabLayout.OnTabSelectedListener () {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                viewPager.setCurrentItem (tab.getPosition ());
            }
        });
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId ())
        {
            case R.id.menu_edit:
                Intent intent = new Intent(EventDetailActivity.this, EditEventActivity.class);
                intent.putExtra(Event.EVENT_KEY, currentEvent); // TODO Event via serialization, or event id only ?
                //intent.putExtra("id", currentEvent.getId());
                startActivityForResult(intent, EDIT_EVENT_REQUEST);
                return true;
            ... other cases
        }
    }

    @Override
    protected void onActivityResult (int requestCode, int resultCode, Intent data)
    {
        if (requestCode == EDIT_EVENT_REQUEST)
        {
            switch (resultCode)
            {
                case RESULT_CANCELED:
                    // Nothing to do
                    return;
                case RESULT_EVENT_MODIFIED:
                    // Cause event view for this activity to update.
                    // When the edit activity was started, the Event was serialized.
                    // An updated Event is passed back in the result.
                    //currentEvent = (Event)data.getSerializableExtra(Event.EVENT_KEY);
                    //System.out.println("Modified event returned: " + currentEvent.getEventTitle());
                    // Alternatively, Load the Event from the database:
                    try
                    {
                        HashMap attr = MyApp.getDatabase().getEventById(currentEvent.getId());
                        currentEvent.load(attr);
                        System.out.println("Event reloaded: " + currentEvent.getEventTitle());
                    }
                    catch (PersistenceException ex)
                    {
                        // TODO handle error
                    }
                    // FIXME: In both cases the received event is correct, but the UI is not updated.
                    // The adapter still references the object that was passed to the edit activity as serialized data
                    // So must give the adapter the object just deserialized/loaded here.
                    adapter.setEvent(currentEvent); // ***** notifyDataSetChanged() is called within this, but not updating the view !!!!!!!!
                    return;
                case RESULT_EVENT_UPDATE_FAILED:
                    // Nothing to do
                    return;
            }
        }
    }
    ...
}

The adapter:

public class PagerAdapter extends FragmentStatePagerAdapter
{
    /** The event on display */
    private Event m_event;

    public PagerAdapter (Event event, FragmentManager fm)
    {
        super(fm);
        m_event = event;
    }

    public void setEvent (Event event)
    {
        m_event = event;
        notifyDataSetChanged(); // ****** Attempting to trigger update of displayed data, but the view does not update.
    }

    @Override
    public Fragment getItem (int position)
    {
        Fragment f;
        switch (position)
        {
            case 0:
                f = new DetailsFragment();
                break;
            ... other tab fragments
            default:
                return null;
        }
        // ******* FIXME?: The problem with passing serialized event to the fragment is that the fragment does not reference our event. 
        ... each fragment references a COPY of the event.
       // The updated event is passed back in the result... then set in the adapter.... BUT NOT IN FRAGMENTS 
       ... BUT FRAGMENTS GET CREATED HERE AS NECESSARY TO VIEW, AND WILL GET THE MODIFIED EVENT IN THIS ARGUMENTS BUNDLE:
       Bundle bundle = new Bundle();
       bundle.putSerializable(Event.EVENT_KEY, m_event);
       // Maybe just pass the event id in arguments, and the fragment gets the event from the database?? Sounds inefficient, and I think should not be necessary.
       //bundle.putLong(Event.EVENT_ID_KEY, m_event.getId());
       f.setArguments(bundle);
       return f;
    }
    ...
}

public class DetailsFragment extends Fragment
{
    /** Event to display */
    private Event m_event = null;

    ... UI TextView object declarations to show various attributes ...

    public DetailsFragment() {
        // Required empty public constructor
    }

    private void update ()
    {
        if (m_event == null)
        {
            ... set views empty ...
            return;
        }
        ... set views for attributes of m_event ...
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate (R.layout.fragment_event_details, container, false);

        //initalize widgets ...

        // Get event for view
        Bundle args = getArguments();
        m_event = (Event)args.getSerializable(Event.EVENT_KEY); // ***** Get the event passed in arguments to this fragment
        update();
        return view;
    }
}

Solution

  • Try override method in your FragmentStatePagerAdapter

    @Override
    public int getItemPosition(@NonNull Object object) {
    return POSITION_NONE;
    }