Question

I'm developping an app which is supposed to display the list of talks of a conference, and another list with only the papers of the talks.

I posted a few screenshots in this question : Save checkboxes states through fragments in Android

My aim is to be able to «mirror» the checkboxes state in the two lists, so that if a paper is checked in the talks list, it also gets checked in the papers list.

Do you have any idea how I can do this?

The lists are implemented using two fragments with their own adapter.

Here's my code :

Main activity :

package be.unamur.confpers;
public class MainActivity extends FragmentActivity implements ActionBar.TabListener {

private ViewPager viewPager;
private TabsPagerAdapter mAdapter;
private ActionBar actionBar;
SqlHandler sqlHandler;
// Tab titles
private String[] tabs = { "Talks", "Papers", "Ma sélection"};

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

    // Initialization database
    sqlHandler = new SqlHandler(this);

    viewPager = (ViewPager) findViewById(R.id.pager);
    actionBar = getActionBar();
    mAdapter = new TabsPagerAdapter(getSupportFragmentManager());

    viewPager.setAdapter(mAdapter);
    actionBar.setHomeButtonEnabled(false);
    actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);       

    // Adding Tabs
    for (String tab_name : tabs) {
        actionBar.addTab(actionBar.newTab().setText(tab_name)
                .setTabListener(this));
    }

    /**
     * on swiping the viewpager make respective tab selected
     * */
    viewPager.setOnPageChangeListener(new ViewPager.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
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) {
}



}

TalksFragment :

public class TalksFragment extends Fragment {

private TalkAdapter talkAdapter;
List<Talk> talks;

private List<Talk> talksParser(){

    try{
        XMLParser parser = new XMLParser ();
        talks = parser.parse(getActivity().getApplicationContext().getAssets().open("talks.xml"));
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    return talks;
};


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

    View v = inflater.inflate(R.layout.fragment_talks, container, false);

    ExpandableListView lv = (ExpandableListView) v.findViewById(R.id.listTalks);
    talks = talksParser();
    talkAdapter = new TalkAdapter (getActivity(),talks);
    lv.setAdapter(talkAdapter);


    return v;
}
}

TalkAdapter :

public class TalkAdapter extends BaseExpandableListAdapter {

private Context context;
private List<Talk> talks;
private LayoutInflater inflater;
private Button changeScreen;

int checked = 0;
CheckBox cb;



public TalkAdapter(Context context, 
        List<Talk> talks) { 
    this.context = context;
    this.talks = talks;
    inflater = LayoutInflater.from( context );
}

public Paper getChild(int groupPosition, int childPosition) {
    return talks.get(groupPosition).getPapers().get(childPosition);
}

public long getChildId(int groupPosition, int childPosition) {
    return (long)( groupPosition*1024+childPosition );  // Max 1024 children per group
}

private class ViewHolder {
    TextView title;
    TextView author;
    CheckBox name;
}


public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
    View v = null;
    ViewHolder holder = null;

    if( convertView == null ){
        v = inflater.inflate(R.layout.child_row, parent, false); 
        Paper p = getChild(groupPosition, childPosition );

        cb = (CheckBox)v.findViewById( R.id.check1 );
        //cb.setChecked( p.getState() );

        TextView paper = (TextView)v.findViewById(R.id.papername);
        if( paper != null )
            paper.setText( p.getTitle() );

        TextView author = (TextView)v.findViewById(R.id.authorname );
        if( author!= null )
            author.setText( p.getAuthor() );

        holder = new ViewHolder();
        holder.name = (CheckBox) v.findViewById(R.id.check1);
        holder.title = (TextView) v.findViewById(R.id.papername);
        holder.author= (TextView) v.findViewById(R.id.authorname);
        v.setTag(holder);

        holder.name.setOnClickListener(new View.OnClickListener() {

            public void onClick(View v) {
                // TODO Auto-generated method stub
                CheckBox cb = (CheckBox) v;
                Paper paper = (Paper) cb.getTag();
                String title = paper.getTitle();
                String author = paper.getAuthor();
                if (cb.isChecked()){
                    Toast.makeText(context, "Papier "+title+ " ajouté", Toast.LENGTH_SHORT).show();
                    //String query = "INSERT INTO SELECTED_PAPERS(title,author) values ('"
                    //+ title +"','" + author +"')";
                    //sqlHandler.executeQuery(query);

                }
                if (cb.isChecked()==false){
                    Toast.makeText(context, "Papier "+title+ " retiré", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    else{
        v = convertView;
        holder = (ViewHolder) v.getTag();
    }

    Paper paper = getChild(groupPosition, childPosition);
    holder.author.setText(paper.getAuthor());
    holder.title.setText(paper.getTitle());
    holder.name.setTag(paper);

    return v;


}

public int getChildrenCount(int groupPosition) {
    return talks.get(groupPosition).getPapers().size();
}

public Talk getGroup(int groupPosition) {
    return talks.get(groupPosition);        
}

public int getGroupCount() {
    return talks.size();
}

public long getGroupId(int groupPosition) {
    return (long)( groupPosition*1024 );  // To be consistent with getChildId
} 

public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
    View v = null;
    if( convertView != null )
        v = convertView;
    else
        v = inflater.inflate(R.layout.group_row, parent, false); 
    Talk t = getGroup( groupPosition );
    TextView colorGroup = (TextView)v.findViewById( R.id.papername );
    if( t != null )
        colorGroup.setText( t.getName() );  


    return v;
}   



public boolean hasStableIds() {
    return true;
}

public boolean isChildSelectable(int groupPosition, int childPosition) {
    return true;
} 

public void onGroupCollapsed (int groupPosition) {} 
public void onGroupExpanded(int groupPosition) {}


}

PapersFragment :

public class PapersFragment extends Fragment {

private PaperAdapter listAdapter;
private Context myContext;
List<Paper> papers = null;




private List<Paper> papersParser () {

    try {
        XMLParser2 parser = new XMLParser2();
        papers = parser.parse(getActivity().getApplicationContext().getAssets().open("talks.xml"));
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    return papers;
};



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

    View v = inflater.inflate(R.layout.fragment_papers, container, false);

    ListView lv = (ListView) v.findViewById(R.id.list);
    papers = papersParser();
    listAdapter = new PaperAdapter(getActivity(),papers);
    lv.setAdapter(listAdapter);
    lv.setOnItemClickListener(new OnItemClickListener(){
        public void onItemClick(AdapterView<?> parent, View view,
                 int position, long id) {
            //Si un jour je veux mettre une description
                   }
    });

    if (papers == null) {
    Toast.makeText(getActivity().getApplicationContext(), "Papers vide", Toast.LENGTH_LONG).show();
    }
    return v;
}
}

PaperAdapter :

public class PaperAdapter extends BaseAdapter implements OnClickListener {

private Context context;
private List<Paper> papers;
private LayoutInflater inflater;
CheckBox cb;
TextView paper;
SqlHandler sqlHandler;

public PaperAdapter(Context context, List<Paper> papers) {
    this.context = context;
    this.papers = papers;
    inflater = LayoutInflater.from(context);
}

@Override
public int getCount(){
    return papers.size();
}

@Override
public Object getItem(int position) {
    return papers.get(position);
}

@Override
public long getItemId(int position) {
    return position;
}

private class ViewHolder {
    TextView title;
    TextView author;
    CheckBox name;
}

@Override
public View getView(int position, View view, ViewGroup viewGroup) {

    ViewHolder holder = null;

    // We only create the view if its needed
    if (view == null) {
        view = inflater.inflate(R.layout.child_row, null);

        // Set the click listener for the checkbox
        //view.findViewById(R.id.check1).setOnClickListener(this);


        Paper p = (Paper) getItem(position);

        // Set the example text and the state of the checkbox
        CheckBox cb = (CheckBox) view.findViewById(R.id.check1);
        //cb.setChecked(p.isSelected());
        // We tag the data object to retrieve it on the click listener.

        paper = (TextView)view.findViewById(R.id.papername);
        if (paper != null)
            paper.setText(p.getTitle());

        TextView author = (TextView)view.findViewById(R.id.authorname);
        if( author!= null )
            author.setText( p.getAuthor() );

        holder = new ViewHolder();
        holder.name = (CheckBox) view.findViewById(R.id.check1);
        holder.title = (TextView) view.findViewById(R.id.papername);
        holder.author= (TextView) view.findViewById(R.id.authorname);
        view.setTag(holder);

        holder.name.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                CheckBox cb = (CheckBox) v;
                Paper paper = (Paper) cb.getTag();
                String title = paper.getTitle();
                String author = paper.getAuthor();
                if (cb.isChecked()){
                    Toast.makeText(context, "Papier "+title+ " ajouté", Toast.LENGTH_SHORT).show();
                    //String query = "INSERT INTO SELECTED_PAPERS(title,author) values ('"
                            //+ title +"','" + author +"')";
                    //sqlHandler.executeQuery(query);

                }
                if (cb.isChecked()==false){
                    Toast.makeText(context, "Papier "+title+ " retiré", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
    else{
        holder = (ViewHolder) view.getTag();
    }

    Paper paper = papers.get(position);
    holder.author.setText(paper.getAuthor());
    holder.title.setText(paper.getTitle());
    holder.name.setTag(paper);

    return view;
}

/*@Override
/** Will be called when a checkbox has been clicked. */
public void onClick(View view, int position) {
    /*TextView p = (TextView) view.findViewById(R.id.papername);
    TextView a = (TextView) view.findViewById(R.id.authorname);
    CheckBox cb = (CheckBox) view.findViewById(R.id.check1);
    String title = p.getText().toString();
    String author = a.getText().toString();
    String query = "INSERT INTO SELECTED_PAPERS(title,author) values ('"
            + title +"','" + author +"')";
    sqlHandler.executeQuery(query);*/
    //TextView p = (TextView) view.findViewById(R.id.papername);
    //TextView a = (TextView) view.findViewById(R.id.authorname);
    //String title = p.getText().toString();
    //String author = a.getText().toString();
    Paper p = (Paper) this.getItem(position);
    String title = p.getTitle();
    Toast.makeText(context, "Papier ajouté", Toast.LENGTH_SHORT).show();

}

@Override
public void onClick(View arg0) {
    // TODO Auto-generated method stub

}

/*
private void savePrefs(String key, String checked) {
    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
    Editor edit = sp.edit();
    edit.putString (key, checked);
    edit.commit();

}*/


}

Thanks a lot!

Was it helpful?

Solution

I'm quite busy, but I'll drop you some hints here to help you move forwards.

  1. I'm not touching your adapters to make it more simple but consider unifying as much as you can, if both lists are almost the same but with different getView() methods, use a common base abstract class for both adapters and implement each different getView(). This is just an example.

  2. Beware of that Context that you pass. Don't pass the activity to your adapter. In this particular case there doesn't seem to be any harm, but, the fragment is handled by the FragmentManager, and the Adapter lives in the fragment, so you are potentially leaking memory for a while until the fragment gets really destroyed. Instead pass getActivity().getApplicationContext(); (or better yet, in your adapter's constructor do: this.context = context.getApplicationContext(); (this way you always reference that).

  3. It's really long to put in code here (and I have to go back to work!) but think of the problem this way:

    • Your Activity is hosting two (or more) Fragments.
    • In your fragment(s) you perform an action that modifies the same data as another Fragment
    • You need to notify everyone who is interested that the data has been changed so they can do whatever is needed (update the UI, reload, etc.).

How would I do this

  1. Have a "DataController" class that contains the list of talks.xml already parsed. Anyone could do: DataController.getInstance(context).getListOfTalks();
  2. DataController would check if the list is null and proceed to create/parse then return the value. If the list is not null, then it will return it. Now your consumers of the data have no idea where it comes from and it always comes from the same place.
  3. Have your Adapters fetch the data from the DataController as outlined in #2.
  4. Now that the data is in a central repository, have your data controller implement methods to modify/persist the data (i.e. if you use SQL that code should be in that controller).
  5. Have DataController implement a Listener/Observer pattern or similar so interested parties can subscribe and be notified.
  6. When a fragment/onclick needs to check a box, it tells the DataController, which in turns saves this new data, then proceeds to fire a notification to its listeners.
  7. Each fragment subscribes to the DataController upon startup (and removes itself during onStop() for example).
  8. When the fragments receive this "notification" they proceed to tell the adapter to update the data.

It sounds like a lot but you'd be gaining a lot of good things. Separating the data from the view, unifying places, establishing a common communication pattern, etc.

Some pseudo code of this DataController would be…

public class DataController {
    private static DataController sDataController;
    private final Context mAppContext;
    private final List<DataChangeListener> mListeners = new ArrayList<DataChangeListener>();
    private List<Talks> mData;
    private DataController(final Context appContext) {
        mAppContext = appContext;
    }
    public static DataController get(Context context) {
        if (sDataController == null) {
            if (context != null) {
                sDataController = new DataController(context.getApplicationContext());
            } else {
                sDataController = new DataController(YourAppClass.getInstance().getApplicationContext());
            }
        }
        return sDataController;
    }
    /**
     * {@link DataChangeListener} listeners will be notified when certain requests have been made.
     *
     * @param listener - a valid DataChangeListener. If null, nothing is added.
     */
    public void addDataChangeListener(DataChangeListener listener) {
        if (listener != null && !mListeners.contains(listener)) {
            mListeners.add(listener);
        }
    }
    /**
     * Remove a {@link DataChangeListener} from the list.
     *
     * @param listener - a valid DataChangeListener. If null, nothing is removed.
     */
    public void addDataChangeListener(DataChangeListener listener) {
        if (listener != null) {
            mListeners.remove(listener);
        }
    }

// INTERESTING METHODS YOU HAVE TO IMPLEMENT

    public List<Talks> getData(){
        if ( mData == null ) {
            parseData();// implement this 
        }
        return mData;
    }
    public void checkBoxChanged(final somedatatype somedata) {
        // pass the appropriate values you need and save them to your sql, update the list of talks
        // talks should contain the value of the checkbox, add it to your Talks object if it's not there.
        updateSQL();
        notifyDataChnged();
    }
    public void notifyDataChnged(){
        if (mListeners != null) {
            for (DataChangeListener listener : mListeners) {
                listener.onDataSetChanged();
            }
        }
    }
}

This is the interface:

public interface DataChangeListener {
    void onDataSetChanged();
}

Now your Fragments would do something like:

    @Override
    public void onResume() {
        super.onResume();
        DataController.getInstance(context).addDataChangeListener(this);
    }
    @Override
    public void onPause() {
        super.onPause();
        DataController.getInstance(context).removeDataChangeListener(this);
    }

And of course they implement the interface…

public class PapersFragment extends Fragment implements DataChangeListener {

and the obliged method…

@Override
public void onDataSetChanged(){
 // Data has changed, do something about it, like telling the adapter and doing anything you see fit.
}

I think this is a good starting point that will lead you to better architecture.

Good luck!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top