0

So I'm creating this app that allows users to search for recipes by ingredients, categories, and preparation time.

Initially, I had 3 activities:

  • IngredientActivity, where the user chooses ingredients they have.
  • CategoryActivity, where they can choose multiple food categories from a list.
  • TimeActivity, which allows users to choose maximum preparation time.

However, it was such a hassle this way as I had to pass the data the user chose with an Intent to the next activity with a next button, and it was a mess always getting and adding extras until I got to the last activity, plus I wanted to be able to move freely between the 3 "pages", and not be restricted to going through them one by one.

This didn't seem efficient to me so I decided to change those activities into fragments and use a ViewPager to display them in tabs in a host activity (MainActivity), but it seems it's a different kind of hassle now.

I have a Search button in the MainActivity, and I'm having difficulty getting the data the user chose from all 3 fragments all at once when the Search button is clicked. I read about interfaces, but I'm not sure if it's the solution. I thought maybe I could define an OnSearchClickListener interface in all 3 fragments, but can I implement one interface for 3 fragments, with each fragment returning different data?

Did I make a mistake transitioning to fragments? However, it seemed the most efficient way to do it... How can I get all the data from the fragments when the search button is clicked?

user3501779
  • 157
  • 1
  • 10

2 Answers2

1

Note: updated upon clarifications in comments

I would do the following:

  1. In each of the three fragments, implement method getSearchCriteria, with each returning value specific to that fragment.

  2. Implement one OnClickListener for the search button - at the activity level.

  3. Inside that listener, call getSearchCriteria on each of the fragments - and do whatever you need to do with all the collated results, something like this:

    findViewById(R.id.button_search).setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            List<String> ingredients = ingredientFragment.getSearchCriteria();
            List<String> categories = categoryFragment.getSearchCriteria();
            int maxMinutes = timeFragment.getSearchCriteria();
    
            // now you have all three things together - do what you need to with them
        }
    });
    
Aleks G
  • 56,435
  • 29
  • 168
  • 265
  • Thanks for the reply. I meant that I need to search in a database once I get all of the data the user entered. I'm not searching in individual fragments, but I'm using the data in the fragments (collectively) to search in a database. Actually, I'm planning to pass the search criteria to another activity which will do the searching, but that's besides the point I guess. – user3501779 Oct 20 '16 at 20:13
  • Then I don't quite understand the application flow. Where is the user entry happening? What do you need to do with the three fragments? Get info from each of them or display results in each of them? – Aleks G Oct 20 '16 at 20:15
  • The user chooses ingredients in the IngredientFragment, categories in CategoryFragment, and time in TimeFragment. I want to get info from each once I click "Search" and then display the results in a different activity. – user3501779 Oct 20 '16 at 20:19
  • Did you get what I'm trying to do? – user3501779 Oct 20 '16 at 20:48
  • @user3501779 In this case, the idea of my answer stands - I've updated it to make it clearer – Aleks G Oct 20 '16 at 21:04
  • Thank you a lot, I don't know how I didn't think about this... Anyhow, how can I get the fragments? Do I need to assign an ID for them and get them using this ID? – user3501779 Oct 20 '16 at 21:13
  • @user3501779 How you get the fragments depends on how they are added to the screen. As you seem to always have these three fragments, create them in the `onCreate` from your activity and keep them in class-level variables. – Aleks G Oct 20 '16 at 21:15
  • I have the fragments as tabs in a ViewPager, and they're created in the ViewPager class... How can I create them in the `onCreate` of the activity? – user3501779 Oct 20 '16 at 21:45
1

If you notify the MainActivity every time the criteria is updated, you can update their respective criterias and have them available to use when searching (see onSearchClicked)

IngredientFragment.java

public class IngredientFragment extends Fragment {
    EditText editText;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_ingredient, container, false);
        editText = (EditText)view.findViewById(R.id.edit_text);//assuming user type in the criteria in an edit box
        return view;
    }

    public void onClick(View view){//user interaction to signal criteria updated. Replace this with onItemClickListener etc, if you are using ListView
        if (mListener != null) {
            mListener.onIngredientCriteriaUpdated(String.valueOf(editText.getText()));
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnIngredientFragmentListener) {
            mListener = (OnIngredientFragmentListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnIngredientFragmentListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public interface OnIngredientFragmentListener {
        // TODO: Update argument type and name
        void onIngredientCriteriaUpdated(String criteria);
    }
}

CategoryFragment.java

public class CategoryFragment extends Fragment {
    EditText editText;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_category, container, false);
        editText = (EditText)view.findViewById(R.id.edit_text);//assuming user type in the criteria in an edit box
        return view;
    }

    public void onClick(View view){//user interaction to signal criteria updated. Replace this with onItemClickListener etc, if you are using ListView
        if (mListener != null) {
            mListener.onCategoryCriteriaUpdated(String.valueOf(editText.getText()));
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnCategoryFragmentListener) {
            mListener = (OnCategoryFragmentListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnCategoryFragmentListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public interface OnCategoryFragmentListener {
        // TODO: Update argument type and name
        void onCategoryCriteriaUpdated(String criteria);
    }
}

TimeFragment.java

public class TimeFragment extends Fragment {
    EditText editText;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_category, container, false);
        editText = (EditText)view.findViewById(R.id.edit_text);//assuming user type in the criteria in an edit box
        return view;
    }

    public void onClick(View view){//user interaction to signal criteria updated
        if (mListener != null) {
            mListener.onTimeCriteriaUpdated(String.valueOf(editText.getText()));
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnTimeFragmentListener) {
            mListener = (OnTimeFragmentListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnTimeFragmentListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public interface OnTimeFragmentListener {
        // TODO: Update argument type and name
        void onTimeCriteriaUpdated(String criteria);
    }
}

MainActivity.java (partial code)

public class MainActivity extends AppCompatActivity implements
    IngredientFragment.OnIngredientFragmentListener,
    CategoryFragment.OnCategoryFragmentListener,
    TimeFragment.OnTimeFragmentListener {

    private String ingredientCriteria;
    private String categoryCriteria;
    private String timeCriteria;

    :
    :
    :

    @Override
    public void onIngredientCriteriaUpdated(String criteria) {
        ingredientCriteria = criteria;
    }

    @Override
    public void onCategoryCriteriaUpdated(String criteria) {
        categoryCriteria = criteria;
    }

    @Override
    public void onTimeCriteriaUpdated(String criteria) {
        timeCriteria = criteria;
    }

    public void onSearchClicked(View view){//handler for your search button
        //do search using value of ingredientCriteria + categoryCriteria + timeCriteria
    }
}
Short answer
  • 144
  • 7
  • I had a thought to do it this way, with interfaces. However, my ingredients and categories are Lists of strings and not strings (you type in an Edittext but they are added in a List). I tried endlessly looking for a method that listens to List changes, to no avail. I mean with an Edittext it's pretty straightforward, but not so with Lists. How can I do it with a List? – user3501779 Oct 21 '16 at 18:09
  • Is it like a listview with checkbox [http://stackoverflow.com/questions/18162931/get-selected-item-using-checkbox-in-listview]? If you're using ListView, instead of triggering the update via *onClick*, replace it with *onItemClick*. Upon *onItemClick*, populate an array with all checked values and pass that array back to MainActivity via the listener's function. You need to change the listener's function to accept List and the criteria to be of type List. – Short answer Oct 22 '16 at 09:50
  • I am using a ListView, which is initially empty. I have an AutoCompleteTextView which displays ingredients as you type, and once you click on one of the options it is added to the ListView, with a delete button for each item to allow removing of ingredients. I found a way to listen to List changes using a DatasetChangeListener, which I implement in the onCreateView method of the fragment, however I'm not sure if this is the correct way to do it. Is this way acceptable, or are there better alternatives? – user3501779 Oct 22 '16 at 19:24
  • Oh, I'm sorry. I meant that I used a DataSetObserver, not a DatasetChangeListener. – user3501779 Oct 22 '16 at 21:09
  • To populate a ListView, I usually go for the BaseAdapter approach [https://www.raywenderlich.com/124438/android-listview-tutorial]. This way, I can easily retrieve the content of the list. – Short answer Oct 23 '16 at 02:37
  • I'm using an ArrayAdapter, and using a DataSetObserver to know when changes have been made to the list, in order to send the list to the MainActivity using the interface we defined earlier. I'm asking if there is any other way to do it. It's working this way though... – user3501779 Oct 23 '16 at 09:01
  • In that case, can you call the listener's function when you add/remove item from the ArrayAdapter? – Short answer Oct 23 '16 at 12:46