Android: Best Approach to pass data between viewpa

2019-01-15 19:34发布

问题:

I have 3 fragments in a ViewPager Activity. All 3 fragments have input fields. Here I am trying to pass first two fragments data to third fragment. I read few posts here and most of them suggested to use interfaces(i.e. to pass data through parent activity) I have also gone through this link http://developer.android.com/training/basics/fragments/communicating.html

Interface: using interfaces is good approach when we are sending data through some user event. Here I am trying to send data without any user event. Hence I thought of onPause() since onPause() is always called. But ViewPager functions differently. When a fragment is loaded,the adjacent fragments are also loaded. I would be successful to pass data between 1st fragment to 3rd fragment. But 2nd fragment's onPause() wont be called unless I am navigating to some fragment that is not adjacent to it(which in my case is not there)

Setter/Getters:I have read in few posts people saying not to use setter/getters(I still havent understood the reason yet) Are getters and setters poor design? Contradictory advice seen

Bundle: I havent considered this yet. Since I am again confused here how would I pass data using bundle.(inside which method should I send data? and how?)

Sorry if my question sounds dumb.I am trying to understand fragments and i would like to know best way to pass data between fragments in viewpager. Thank You in advance.

TabPAgerAdapter -- >

  package com.jbandroid.model;

import com.jbandroid.fragment.LocationInfoFragment;
import com.jbandroid.fragment.PersonalInfoFragment;
import com.jbandroid.fragment.PostInfoFragment;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

public class TabsPagerAdapter extends FragmentPagerAdapter {

    public TabsPagerAdapter(FragmentManager fm){
        super(fm);
    }


    @Override
    public Fragment getItem(int index) {

        switch(index) {

        case 0 : //PostInfoFragment
                         return new PostInfoFragment();

        case 1 : //LocationInfoFragment
                        return new LocationInfoFragment();

        case 2 : //PersonalInfoFragment
                         return new PersonalInfoFragment();

        }

        return null;
    }

    @Override
    public int getCount() {
        // get item count - equal to number of tabs
        return 3;
    }

}

ViewPagerActivity -- >

 package com.jbandroid;

public class SubmitPostActivity extends FragmentActivity implements ActionBar.TabListener,PostInfoFragment.setPostInfo,LocationInfoFragment.setLocationInfo{

    private ViewPager viewpager;
    private ActionBar actionBar;
    private TabsPagerAdapter mAdapter;
     FragmentManager manager;
     PersonalInfoFragment frag;
     List<String> location;
    /*private MenuItem myActionMenuItem;
      private Button myActionButton;*/


    //Tab titles
    private String[] tabs  = {"Post Info" , "Location Info" , "Personal Info" };



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

        viewpager = (ViewPager) findViewById(R.id.pager);
        actionBar = getActionBar();
        manager = getSupportFragmentManager();
        mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
        //viewpager.setOffscreenPageLimit(2);
        viewpager.setAdapter(mAdapter);

        //actionBar.setHomeButtonEnabled(false);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        for (String tab : tabs){
            actionBar.addTab(actionBar.newTab().setText(tab).setTabListener(this));
        }

        if(savedInstanceState != null){
            actionBar.setSelectedNavigationItem( savedInstanceState.getInt("tab",0));
        }

        /**
         * on swiping the viewpager make respective tab selected
         * */

        viewpager.setOnPageChangeListener(new OnPageChangeListener() {

            @Override
            public void onPageSelected(int position) {
                // on changing the page
                // make respected tab selected
                actionBar.setSelectedNavigationItem(position);
            }

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {

            }

            @Override
            public void onPageScrollStateChanged(int arg0) {

            }
        });
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
    }



    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {

    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        // on tab selected
        // show respected fragment view

        viewpager.setCurrentItem(tab.getPosition());

    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {

    }



    @Override
    public void pass_location_details(List<String> location) {

         frag = (PersonalInfoFragment) manager.findFragmentByTag("android:switcher:" + viewpager.getId() + ":" + 2);
        frag.get_post_location_details(location);
        Log.d("submitarea", location.get(0));
    }


    @Override
    public void pass_post_details(List<String> post_details,ArrayList<CustomGallery> selected) {
         frag = (PersonalInfoFragment) manager.findFragmentByTag("android:switcher:" + viewpager.getId() + ":" + 2);
            frag.get_post_details(post_details,selected);
            Log.d("submitpostinfo","hello"+ post_details.get(5));
    }
    }

1st Fragment(Here I am trying to pass data using interface in onPause()-->

    package com.jbandroid.fragment;
public class PostInfoFragment extends Fragment {

    private MenuItem myActionMenuItem;
    private Button myActionButton;
    private ActionBar actionBar;
    private String post_title, post_desc,post_status;

    private EditText submit_post_title, submit_post_desc;

        private Resources res;

    setPostInfo info;
    List<String> post_details;


    //RelativeLayout rel_submit_post_start_date,rel_submit_post_end_date;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_post_info,
                container, false);

        /*if(!imageLoader.isInited()){*/
        initImageLoader();
        /*}*/

        //handler = new Handler();


        submit_post_title = (EditText) rootView
                .findViewById(R.id.submit_post_title);
        submit_post_desc = (EditText) rootView
                .findViewById(R.id.submit_post_description);

        actionBar = getActivity().getActionBar();
        setHasOptionsMenu(true);

        post_details = new ArrayList<String>();
        res = getResources();

        setListeners();

        Log.d("postinfo_oncreate view", "postinfo_oncreate view");

        return rootView;

    }



    //interface to pass data to activity and then to PersonalInfoFragment
    public interface setPostInfo {
        //public void pass_post_details(List<String> post_details);
        public void pass_post_details(List<String> post_details,ArrayList<CustomGallery> selected);
    }


    //making sure if the parent activity has implemented interface
    @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 {
            info = (setPostInfo) activity;
        } catch (ClassCastException e) {

            throw new ClassCastException(activity.toString()
                    + "must implemet setPostInfo");
        }

         Log.d("postinfo_onattach", "postinfo_onattach");
    }


    //passing form inputs to personalinfofragments
    @Override
    public void onPause() {
        super.onPause();
        // setFormInputs();

        passFormInputs();  ---> passing in onPause() This executes successfully

         Log.d("postinfo_onPAuse", "postinfo_onPause");

    }


    //method to pass data to personalinfofragment
    private void passFormInputs() {
        try {
            post_title = submit_post_title.getText().toString();
            post_desc = submit_post_desc.getText().toString();
            post_status = "1";

            if(post_title != null && post_title.length() > 0 

                    && post_desc != null && post_desc.length() > 0
                    && post_status != null && post_status.length() > 0
                    ){
            post_details.add(post_title);
            post_details.add(post_desc);
            post_details.add(post_status);

            info.pass_post_details(post_details,dataT); -->here I am passing values via             
            }else{                                                activity to 3rd fragment
                Log.d("post_info", "values are null");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    //setting next button on actionbar
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);

        // Inflate the menu items for use in the action bar

        inflater.inflate(R.menu.mymenu, menu);

        // Here we get the action view we defined
        myActionMenuItem = menu.findItem(R.id.my_action);
        View actionView = myActionMenuItem.getActionView();

        // We then get the button view that is part of the action view
        if (actionView != null) {
            myActionButton = (Button) actionView.findViewById(R.id.action_btn);
            myActionButton.setText(R.string.txt_next);
            if (myActionButton != null) {
                // We set a listener that will be called when the return/enter
                // key is pressed
                myActionButton.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {

                        actionBar.setSelectedNavigationItem(1);
                    }
                });
            }
        }

    }

}

2nd Fragment-->

   package com.jbandroid.fragment;



public class LocationInfoFragment extends Fragment implements OnClickListener {

    private MenuItem myActionMenuItem;
    private Button myActionButton;
    private ActionBar actionBar;

    Dialog dialog;
    private EditText submit_post_exact_location;
    private TextView selected_country, selected_city, 
            submit_post_exact_time;
    String country, city, exact_location, exact_time;
    setLocationInfo info;

    List<String> location;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        View rootView = inflater.inflate(R.layout.fragment_location_info,
                container, false);
        actionBar = getActivity().getActionBar();
        setHasOptionsMenu(true);


        submit_post_exact_location = (EditText) rootView
                .findViewById(R.id.submit_post_exact_location);
        submit_post_exact_time = (TextView) rootView
                .findViewById(R.id.submit_post_exact_time);

        selected_country = (TextView) rootView
                .findViewById(R.id.selected_country);
        selected_city = (TextView) rootView.findViewById(R.id.selected_city);



        location = new ArrayList<String>();
        setListeners();

        return rootView;

    }

    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);

        // Inflate the menu items for use in the action bar

        inflater.inflate(R.menu.mymenu, menu);

        // Here we get the action view we defined
        myActionMenuItem = menu.findItem(R.id.my_action);
        View actionView = myActionMenuItem.getActionView();

        // We then get the button view that is part of the action view
        if (actionView != null) {
            myActionButton = (Button) actionView.findViewById(R.id.action_btn);
            myActionButton.setText(R.string.txt_next);
            if (myActionButton != null) {
                // We set a listener that will be called when the return/enter
                // key is pressed
                myActionButton.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {


                        actionBar.setSelectedNavigationItem(2);

                    }
                });
            }
        }
    }


    public interface setLocationInfo {
        public void pass_location_details(List<String> location);
    }

    @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 {
            info = (setLocationInfo) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + "must implement setLocationInfo");
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        //setLocationDetails();
    }

    @Override
    public void onPause() {
        super.onPause();
         setLocationDetails();   ----> doesnt executes since onPause isnt called when I                                      navigate to 3rd fragment as it is an adjacent fragment of this fragment
        // Log.d("location : onPause", area);
    }

    private void setLocationDetails() {
        try {

            exact_location = submit_post_exact_location.getText().toString();
            exact_time = submit_post_exact_time.getText().toString();
country = selected_country.getText().toString();
city =  selected_city.getText().toString();

            if (country != null && country.length() > 0
                    && !country.equalsIgnoreCase("select") && city != null
                    && city.length() > 0 && !city.equalsIgnoreCase("select")

                    && exact_location != null && exact_location.length() > 0
                    && exact_time != null && exact_time.length() > 0) {

                location.add(country);
                location.add(city);

                location.add(exact_location);
                location.add(exact_time);
                info.pass_location_details(location);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

In my 3rd Fragment I am trying to get this values

         public class PersonalInfoFragment extends Fragment {
    List<String> post_details;
    List<String> location;
    Button submit;
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment_personal_info,
                    container, false);
    submit = (Button)rootView.findViewById(R.id.submitBtn);
    submit.setOnClickListener(new OnClickListener() {

                        @Override
                        public void onClick(View v) {

                            //performing operations with the values obtained
                            setPostItems();

                            insertintodb();


                        }
                    });

    return rootView;
    }

    public void get_post_details(List<String> post_details,
                ArrayList<CustomGallery> selected) {            -->receiving values from
            this.post_details = post_details;                         1st fragment
            this.selected = selected;
            Log.d("personalfrag(postinfo)", "hello" + post_details.get(5));
        }


//receiving values from 2nd fragment
        public void get_post_location_details(List<String> location) {
            this.location = location;
            Log.d("personalfrag(locationinfo)", "hello" + location.get(0));
        }


    }

回答1:

Okay, I Had same issue to pass data(not just string) between two tabs in a ViewPager. So here is what i did. I Use interfaces to communicate between the different components.

The data passes this way:

Tab 1 -> Activity -> VewPageAdapter -> Tab 2
  1. In Tab 1

create an interface.

OnCartsDataListener mOncarOnCartsDataListener;


public interface OnCartsDataListener {

    public void onCartsDataReceived(ArrayList<CartsViewModel> cartsViewModels);

}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        mOncarOnCartsDataListener = (OnCartsDataListener)activity;
    }catch (ClassCastException e){

    }

}
// now call mOncarOnCartsDataListener.onCartsDataReceived(data) when you have the data
  1. In Activity

Implement the interface and override the method

ViewPagerAdapter adapter;
adapter = new ViewPagerAdapter(getSupportFragmentManager(), Titles, Numboftabs);

@Override
public void onCartsDataReceived(ArrayList<CartsViewModel> cartsViewModels) {
    Log.d(TAG, "data received to Activity... send to view pager");
    adapter.onCartsDataReceived(cartsViewModels);
}

3.IN ViewPagerAdapter

Also implements the interface and override the method

@Override
public void onCartsDataReceived(ArrayList<CartsViewModel> cartsViewModels) {
    Log.d(TAG, "data received to view pager... sending to tab 2");

    if(tab2!=null){
        tab2.onCartsDataReceived(cartsViewModels);
    }else{
        Log.d(TAG, "tab2 is null");
    }
} 
  1. Finally tab 2

Also implements the interface and override the method

@Override
public void onCartsDataReceived(ArrayList<CartsViewModel> cartsViewModels) {
    Log.d(TAG, "Finally ! received data to tab 2");
    if(cartsViewModels!=null){
        for(CartsViewModel cart : cartsViewModels){
            Log.d(TAG,"got it :"+cart.getCartName());
        }
    }
}


回答2:

Since AndroidX, you can create a ViewModel and share data between Activity and all fragments within ViewPager

Read here how to



回答3:

Can you do something like this? First create any data structure like Arraylist in your main activity. Then send a reference of that data model to your fragments. Now update that data when, on change your text fields. By doing this all the fragment can see updated values. So fragments can update this data itself and we don't need to send that data since it is already shared. I'll explain this using your example. Try to improve this. You can maintain fragment specific data model then each fragment can access data with the knowledge of that data owner.

TabsPagerAdapter.java

public class TabsPagerAdapter extends FragmentPagerAdapter {

    public TabsPagerAdapter(FragmentManager fm,SubmitPostActivity activity){
        super(fm);
    }


    @Override
       public Fragment getItem(int index) {

       switch(index) {

          case 0 : //PostInfoFragment
                     return new PostInfoFragment(0,activity);

          case 1 : //LocationInfoFragment
                    return new LocationInfoFragment(1,activity);

          case 2 : //PersonalInfoFragment
                     return new PersonalInfoFragment(2,activity);

        }
        return null;
    }

    @Override
    public int getCount() {       
       return 3;
    }

}

ViewPagerActivity -- >

package com.jbandroid;

public class SubmitPostActivity extends FragmentActivity implements ActionBar.TabListener,LocationInfoFragment.setLocationInfo{

    private ViewPager viewpager;
    private ActionBar actionBar;
    private TabsPagerAdapter mAdapter;
     FragmentManager manager;
     PersonalInfoFragment frag;
     List<String> location;
    /*private MenuItem myActionMenuItem;
      private Button myActionButton;*/


    //Tab titles
    private String[] tabs  = {"Post Info" , "Location Info" , "Personal Info" };
    public List<String> dataModel = new ArrayList<String>();


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

        viewpager = (ViewPager) findViewById(R.id.pager);
        actionBar = getActionBar();
        manager = getSupportFragmentManager();
        mAdapter = new TabsPagerAdapter(getSupportFragmentManager(),this);
        //viewpager.setOffscreenPageLimit(2);
        viewpager.setAdapter(mAdapter);

        //actionBar.setHomeButtonEnabled(false);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        for (String tab : tabs){
            actionBar.addTab(actionBar.newTab().setText(tab).setTabListener(this));
        }

        if(savedInstanceState != null){
            actionBar.setSelectedNavigationItem( savedInstanceState.getInt("tab",0));
        }

    }

}

1st Fragment =>

public class PostInfoFragment extends Fragment {

    private MenuItem myActionMenuItem;
    private Button myActionButton;
    private ActionBar actionBar;
    private String post_title, post_desc,post_status;

    private EditText submit_post_title, submit_post_desc;
    private int position;
    private Resources res;

    SubmitPostActivity callingActivity;
    List<String> post_details;

    public PostInfoFragment(int position,SubmitPostActivity callingActivity )
    {
        this.callingActivity = callingActivity;
        this.position = position;
    }

    //RelativeLayout rel_submit_post_start_date,rel_submit_post_end_date;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_post_info,
                container, false);

        /*if(!imageLoader.isInited()){*/
        initImageLoader();
        /*}*/

        //handler = new Handler();


        submit_post_title = (EditText) rootView
                .findViewById(R.id.submit_post_title);
        submit_post_desc = (EditText) rootView
                .findViewById(R.id.submit_post_description);

        actionBar = getActivity().getActionBar();
        setHasOptionsMenu(true);

        post_details = new ArrayList<String>();
        res = getResources();

        setListeners();

        Log.d("postinfo_oncreate view", "postinfo_oncreate view");

        //this is editText onchange listner do the same for submit_post_desc as well
        submit_post_title.addTextChangedListener( new TextWatcher()
        {

            @Override
            public void onTextChanged( CharSequence s, int start, int before, int count )
            {

            }

            @Override
            public void beforeTextChanged( CharSequence s, int start, int count, int after )
            {
            }

            @Override
            public void afterTextChanged( Editable s )
            {
                if( callingActivity != null )
                {
                //use this.position in order to update relevant data 
                    List<String> post_details = callingActivity.dataModel;
                    if( post_details == null )
                    {
                        post_details = new ArrayList<String>();
                    }
                    post_details.add(s.toString());
                }
            }
        } );


        return rootView;

    }

    //making sure if the parent activity has implemented interface
    @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 {
            callingActivity = (SubmitPostActivity) activity;
        } catch (ClassCastException e) {

            throw new ClassCastException(activity.toString()
                    + "must implemet setPostInfo");
        }

         Log.d("postinfo_onattach", "postinfo_onattach");
    }
}

Please not that this may not compile as it is. Try to get the concept.