What is the best practice around inheriting a custom fragment - android

I'm making an Reddit app for my android exam and I have a question about inheritence.
I have a Fragment who has a RecyclerView. That recyclerview contains a list of redditposts. My app consists of multiple subreddits (funny, gaming, news, etc..). Every subreddit has his own Fragment. I have some methods that every Fragment has to have. (a showProgressBar, hideProgressBar, populateResult, etc...) I think it would be simple if i just make an Fragment class where all the subreddit Fragments can inheritance from. I could put all the methods in that fragment class because the methods are the same for every subreddit fragment. But my lecturer said that is a bad use of inheritance. So does anybody have a best practice around this problem?
This is the fragment i'm talking about:
package com.example.thomas.redditapp;
public class FunnyFragment extends Fragment {
private OnListFragmentInteractionListener mListener;
#Bind(R.id.funny_recyclerview)
RecyclerView mRecyclerView;
#Bind(R.id.progressBarFetch)
ProgressBar progress;
private RedditHelper helper;
private RedditPostRecyclerViewAdapter mAdapter;
List<RedditPost> redditPosts;
public FunnyFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
helper = null;
helper = new RedditHelper(SubRedditEnum.funny, this);
redditPosts = new ArrayList<>();
startLoad();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_funny_list, container, false);
ButterKnife.bind(this, view);
showProgressBar();
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mAdapter = new RedditPostRecyclerViewAdapter(redditPosts, mListener, mRecyclerView);
mAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
#Override
public void onLoadMore() {
redditPosts.add(null);
helper.loadListFromUrl();
}
});
mRecyclerView.setAdapter(mAdapter);
return view;
}
protected void startLoad() {
if (helper != null) {
helper.loadListFromDb();
}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
if (isTaskRunning()) {
showProgressBar();
} else {
hideProgressBar();
}
super.onActivityCreated(savedInstanceState);
}
public void hideProgressBar() {
progress.setVisibility(View.GONE);
}
public void showProgressBar() {
progress.setVisibility(View.VISIBLE);
progress.setIndeterminate(true);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnListFragmentInteractionListener) {
mListener = (OnListFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnListFragmentInteractionListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public void populateResult(List<RedditPost> result) {
if(!redditPosts.isEmpty()){
redditPosts.remove(redditPosts.size() - 1);
}
redditPosts.addAll(result);
mAdapter.setLoaded();
mAdapter.notifyDataSetChanged();
}
protected boolean isTaskRunning() {
if (helper == null) {
return false;
} else if (helper.getStatus() == 0) {
return false;
} else {
return true;
}
}
}
I call the hideProgressBar(), showProgressBar() and populateResult() in my helper class.

There's a long standing mantra in programming that states: "Favor composition over inheritance"
You can read about the details of this statement and a lot of discussion here.
In this case, inheritance is unnecessary because you can simply build 1 Fragment and, on initialization pass it the subreddit, thus avoiding any constraining links between a super and subclass that may not even have any sort of polymorphic relationship.

Related

SavedInstance of multiple customViews inside one fragment

I have multiple instances of the same CustomView inside one fragment.
I implemented savedInstance for this CustomView but the problem is since there are multiple instances of this CustomView, savedInstance of the last one, overrides them all.
for example, if there are 3 instances of this CustomView which has a recyclerview inside, If I scroll the last one, it applies to them all. because i'm using key value pairs and the key is the same for all of them. (I can change the key to differ for each one but I think there is a better way)
Here is the code for savedInstance saving and restoring inside my CustomView:
#Nullable
#Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable(SavedInstanceKey.SUPERSTATE.name(), super.onSaveInstanceState());
bundle.putParcelable(SavedInstanceKey.RECYCLERVIEW.name(), recyclerView.getLayoutManager().onSaveInstanceState()); // ... save stuff
return bundle;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) // implicit null check
{
Bundle bundle = (Bundle) state;
this.recyclerView.getLayoutManager().onRestoreInstanceState(bundle.getParcelable(SavedInstanceKey.RECYCLERVIEW.name())); // ... load stuff
state = bundle.getParcelable(SavedInstanceKey.SUPERSTATE.name());
}
super.onRestoreInstanceState(state);
}
and here is my fragment's OnCreateView:
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_artist, container, false);
final GridListView gv_new = view.findViewById(R.id.gridlist_new_songs);
final GridListView gv_best = view.findViewById(R.id.gridlist_best);
final GridListView gv_singles = view.findViewById(R.id.gridlist_singles);
final GridListView gv_feats = view.findViewById(R.id.gridlist_feats);
final RecyclerView rc_albums = view.findViewById(R.id.rcview_album);
if(!alreadyInitialized) {
alreadyInitialized = true;
apiService = new ApiService(getContext());
try {
artistID = getArguments().getString(KeyIntent.ARTIST.name());
} catch (Exception e) {
Log.e(TAG, "onCreateView: Artist Fragment doesnt have args.\t", e);
}
apiService.getArtist(artistID, new ApiService.OnArtistReceived() {
#Override
public void onSuccess(Artist artist) {
ArtistFragment.this.artist=artist;
setArtistToViews(artist, view);
}
#Override
public void onFail() {
Toast.makeText(getContext(), "Error on receiving artist.", Toast.LENGTH_LONG).show();
}
});
apiService.getNewSongs(artistID, new ApiService.OnSongsReceived() {
#Override
public void onSuccess(List<Song> songs) {
ArtistFragment.this.newSongs=songs;
List<GridListable> gridListables = new ArrayList<>();
gridListables.addAll(songs);
gv_new.load(gridListables, 1);
}
#Override
public void onFail(ApiService.ApiResponse response) {
Toast.makeText(getContext(), "Error on receiving artist.", Toast.LENGTH_LONG).show();
}
});
apiService.getBestSongs(artistID, new ApiService.OnSongsReceived() {
#Override
public void onSuccess(List<Song> songs) {
ArtistFragment.this.bestSongs=songs;
List<GridListable> gridListables = new ArrayList<>();
gridListables.addAll(songs);
gv_best.load(gridListables, 1);
}
#Override
public void onFail(ApiService.ApiResponse response) {
Toast.makeText(getContext(), "Error on receiving artist.", Toast.LENGTH_LONG).show();
}
});
apiService.getSingleSongs(artistID, new ApiService.OnSongsReceived() {
#Override
public void onSuccess(List<Song> songs) {
ArtistFragment.this.singleSongs=songs;
List<GridListable> gridListables = new ArrayList<>();
gridListables.addAll(songs);
gv_singles.load(gridListables, 1);
}
#Override
public void onFail(ApiService.ApiResponse response) {
}
});
apiService.getFeats(artistID, new ApiService.OnSongsReceived() {
#Override
public void onSuccess(List<Song> songs) {
ArtistFragment.this.feats=songs;
List<GridListable> gridListables = new ArrayList<>();
gridListables.addAll(songs);
gv_feats.load(gridListables, 1);
}
#Override
public void onFail(ApiService.ApiResponse response) {
}
});
apiService.getAlbums(artistID, new ApiService.OnAlbumsReceived() {
#Override
public void onSuccess(List<Album> albums) {
ArtistFragment.this.albums=albums;
List<Projective> projectives = new ArrayList<>();
projectives.addAll(albums);
rc_albums.setAdapter(new AlbumAdapter(getContext(), projectives));
rc_albums.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, true));
}
#Override
public void onFail(ApiService.ApiResponse response) {
Toast.makeText(getContext(), "Loading albums failed.", Toast.LENGTH_SHORT).show();
}
});
}else {
Log.i(TAG, "onCreateView: Fragment already initialized, restoring from existing artist");
setArtistToViews(artist,view);
gv_new.load(new ArrayList<>(newSongs),1);
gv_best.load(new ArrayList<>(bestSongs),1);
gv_singles.load(new ArrayList<>(singleSongs),1);
gv_feats.load(new ArrayList<>(feats),1);
rc_albums.setAdapter(new AlbumAdapter(getContext(), new ArrayList<>(albums)));
rc_albums.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, true));
}
return view;
}
I think the problem is the Keys that you use for your Bundles. All your instances of the custom view use the same SavedInstanceKey.SUPERSTATE.name().
You could try to have the Fragment pass a different key to each of the custom views (BEST, NEW...). This way, each of your GridView has its own unique key to use in the saveInstanceState and restoreInstanceState methods.

Why does pressing back from detail activity after landscape-to-portrait-switch show an empty screen?

Below is the MainActivity class that I'm using. The code checks to see if the phone is in landscape or portrait. If it's in portrait, it will show the main fragment in the main activity only (the main fragment is a static fragment in the main_activity.xml file). Then if a "Recipe" is clicked it will open a detail activity with its own fragment. If the phone is in landscape mode, it will show the main fragment and the detail fragment side by side. Everything works perfectly fine however when I follow the procedure below I get a white screen instead of the main activity:
Procedure:
Switch to landscape
Switch back to portrait
Choose an item and wait for the detail activity to open
Press back
Here instead of the main activity window I get a white screen
If I don't switch to landscape and just start with the portrait mode everything is fine. It seems like switching to landscape does something that causes the problem and I can't figure out what. Any tip on what's going on or where to look would be much appreciated.
public class MainActivity extends AppCompatActivity implements RecipesFragment.OnRecipeClickListener {
private String RECIPE_PARCEL_KEY;
private boolean mTwoPane;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RECIPE_PARCEL_KEY = getString(R.string.ParcelKey_RecipeParcel);
if (findViewById(R.id.linearLayoutTwoPane) != null) {
mTwoPane = true;
if (savedInstanceState == null) {
RecipeFragment recipeFragment = new RecipeFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.recipeFrameForTwoPane, recipeFragment)
.commit();
}
} else {
mTwoPane = false;
}
}
#Override
public void OnRecipeClick(Recipe recipe) {
if (mTwoPane) {
RecipeFragment recipeFragment = new RecipeFragment();
recipeFragment.setRecipe(recipe);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.recipeFrameForTwoPane, recipeFragment)
.commit();
} else {
Class destinationClass = DetailActivity.class;
Intent intentToStartDetailActivity = new Intent(this, destinationClass);
intentToStartDetailActivity.putExtra(RECIPE_PARCEL_KEY, recipe);
startActivity(intentToStartDetailActivity);
}
}
}
EDIT:
Adding RecipeFragment's code below:
public class RecipeFragment extends Fragment {
private Recipe mRecipe;
#BindView(R.id.tv_recipeName) TextView recipeNameTextView;
public RecipeFragment(){
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.recipe_fragment,container,false);
ButterKnife.bind(this,view);
if(mRecipe!=null) {
recipeNameTextView.setText(mRecipe.getName());
}else{
recipeNameTextView.setText(getString(R.string.messageSelectARecipe));
}
return view;
}
public void setRecipe(Recipe recipe){
mRecipe = recipe;
}
}
EDIT:
I followed #mt0s's advice and created different background colors for the fragments and activities and finally narrowed down the problem to a line in my recyclerview adapter code. My adapter code is below. Inside loadInBackground() on line URL url = new URL(getString(R.string.URL_RecipeJSON)); I get a Fragment RecipesFragment{96e9b6a} not attached to Activity exception. I don't understand why I'm getting this exception and what the best way to resolve this is. Have I placed the right code in the right fragment methods (ie OnCreate vs OnActivityCreated vs OnCreateView vs etc)?
public class RecipesFragment extends Fragment
implements RecipeAdapter.RecipeAdapterOnClickHandler,
LoaderManager.LoaderCallbacks<ArrayList<Recipe>> {
#BindView(R.id.rv_recipes) RecyclerView mRecyclerView;
private RecipeAdapter mRecipeAdapter;
private static final int LOADER_ID = 1000;
private static final String TAG = "RecipesFragment";
private OnRecipeClickListener mOnRecipeClickListener;
public RecipesFragment(){
}
public interface OnRecipeClickListener {
void OnRecipeClick(Recipe recipe);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.recipes_fragment, container, false);
ButterKnife.bind(this, view);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setHasFixedSize(true);
mRecipeAdapter = new RecipeAdapter(this);
mRecyclerView.setAdapter(mRecipeAdapter);
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
#Override
public void onPause() {
super.onPause();
}
#Override
public void onResume() {
super.onResume();
}
#Override
public void OnClick(Recipe recipe) {
mOnRecipeClickListener.OnRecipeClick(recipe);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try{
mOnRecipeClickListener = (OnRecipeClickListener) context;
} catch (ClassCastException e){
Log.e(TAG, "onAttach: Host activity class must implement OnRecipeClickListener.");
}
}
#Override
public Loader<ArrayList<Recipe>> onCreateLoader(int i, Bundle bundle) {
return new AsyncTaskLoader<ArrayList<Recipe>>(getActivity()) {
#Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad();
}
#Override
public ArrayList<Recipe> loadInBackground() {
String response;
ArrayList<Recipe> recipes = null;
try {
URL url = new URL(getString(R.string.URL_RecipeJSON)); //***I get an exception here***
response = NetworkUtils.getResponseFromHttpUrl(url, getActivity());
recipes = RecipeJsonUtils.getRecipeFromJson(getActivity(), response);
} catch (Exception e) {
Log.e(TAG, "loadInBackground: " + e.getMessage());
}
return recipes;
}
};
}
#Override
public void onLoadFinished(Loader<ArrayList<Recipe>> loader, ArrayList<Recipe> recipes) {
mRecipeAdapter.setRecipeData(recipes);
}
#Override
public void onLoaderReset(Loader<ArrayList<Recipe>> loader) {
}
}
I finally figured out the problem and the solution. The problem is that onStartLoading() in the AsyncTaskLoader anonymous class in RecipesFragment class gets called every time the fragment is resumed whether the enclosing Loader is called or not. This causes the problem. I need to have control over when onStartLoading() is being called and I only want it to be called if and only if the enclosing Loader is being initialized or restarted. As such, I destroyed the loader in onPause() of the fragment and restarted it in onResume(). Hence, I added the following code to the RecipesFragment class:
#Override
public void onPause() {
super.onPause();
getLoaderManager().destroyLoader(LOADER_ID);
}
#Override
public void onResume() {
super.onResume();
getLoaderManager().restartLoader(LOADER_ID, null, this);
}
I also removed initLoader() from onCreate(). This way, every time the fragment is resumed (or created) onStartLoading() will be called. I tried this and it solves my problem.
When you switch from the landscape to portrait or the opposite the Android OS destroy your activity and recreate it again. this what probably trigger your problem

communicating between fragments through activity

I have two fragments FGames and FGamesDetail. which display the list of Games and when clicked should populate the FGamesDetail fragment. I am using MVP pattern.
I am trying to implement MultiPane layout for tablet to have list and detail view next to each other.
I am getting a null pointer exception at 'mListener.onGameSelected(gameEntity);' in FGames. I know I have not initialised it at this place but should I be initialising it every method I go through in MVP pattern.
GamesAdapter - RecyclerView Adapter.
#OnClick(R.id.row_container)
void rowClick(){
GamesPresenter gamesPresenter = new GamesPresenterImpl();
gamesPresenter.showGameDetail(data.get(getLayoutPosition()));
Toast.makeText(context, "itemClicked " + data.get(getLayoutPosition()), Toast.LENGTH_SHORT).show();
}
GamesPresenter - Interface
public interface GamesPresenter {
void initUi();
void showGameDetail(GameEntity gameEntity);
}
GamesPresenterImpl -
public class GamesPresenterImpl implements GamesPresenter {
GamesView gamesView;
private ApiInterface apiInterface;
/**
* Collects all subscriptions to unsubscribe later
*/
#NonNull
private CompositeDisposable mCompositeDisposable = new CompositeDisposable();
public GamesPresenterImpl() {}
public GamesPresenterImpl(GamesView gamesView) {
this.gamesView = gamesView;
}
#Override
public void initUi() {
getGamesData();
}
#Override
public void showGameDetail(GameEntity gameEntity) {
//gamesView was null so initialised here
GamesView gamesView = new FGames();
gamesView.onListItemClick(gameEntity);
}
}
GamesView - interface
public interface GamesView {
/**
* Initialise the recycler view to list Games data
* #param gameEntities
*/
void initRecyclerView(List<GameEntity> gameEntities);
void showToast(String message);
void onListItemClick(GameEntity gameEntity);
}
#
FGames - has all the implementation for the Fragment
public class FGames extends Fragment implements GamesView {
#BindView(R.id.rv_games)
RecyclerView rvGames;
private GamesAdapter gamesAdapter;
private GamesPresenterImpl presenter;
OnGameSelectedListener mListener;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.games_layout, container, false);
ButterKnife.bind(this, view);
presenter = new GamesPresenterImpl(this);
return view;
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mListener = (OnGameSelectedListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement OnArticleSelectedListener");
}
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
presenter.initUi();
}
#Override
public void initRecyclerView(List<GameEntity> gameEntities) {
gamesAdapter = new GamesAdapter(getActivity(), gameEntities);
rvGames.setAdapter(gamesAdapter);
rvGames.setLayoutManager(new LinearLayoutManager(getActivity()));
}
#Override
public void showToast(String message) {
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
}
#Override
public void onListItemClick(GameEntity gameEntity) {
//Here is where the NUll pointer exception is
mListener.onGameSelected(gameEntity);
}
public interface OnGameSelectedListener{
public void onGameSelected(GameEntity gameEntity);
}
}
MainActivity - which displays the performs the game selected operation to update the UI if detail fragment is available. I followed Android documentation to do this.
public class MainActivity extends AppCompatActivity implements FGames.OnGameSelectedListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//FGames fGames = new FGames();
//getSupportFragmentManager().beginTransaction().add(R.id.games_container, fGames).commit();
}
#Override
public void onGameSelected(GameEntity gameEntity) {
FGameDetail gameDetailFrag = (FGameDetail) getSupportFragmentManager()
.findFragmentById(R.id.fragment_fGameDetail);
if (gameDetailFrag == null) {
// DisplayFragment (Fragment B) is not in the layout (handset layout),
} else {
// DisplayFragment (Fragment B) is in the layout (tablet layout),
// so tell the fragment to update
gameDetailFrag.updateContent(gameEntity);
}
}
}
ErrorLog
Process: com.example.rao.igttest, PID: 21481
java.lang.NullPointerException: Attempt to invoke interface method 'void com.example.rao.igttest.Games.View.FGames$OnGameSelectedListener.onGameSelected(com.example.rao.igttest.Games.Entity.GameEntity)' on a null object reference
at com.example.rao.igttest.Games.View.FGames.onListItemClick(FGames.java:73)
at com.example.rao.igttest.Games.Presenter.GamesPresenterImpl.showGameDetail(GamesPresenterImpl.java:53)
at com.example.rao.igttest.Games.View.GamesAdapter$GamesViewHolder.rowClick(GamesAdapter.java:72)
at com.example.rao.igttest.Games.View.GamesAdapter$GamesViewHolder_ViewBinding$1.doClick(GamesAdapter$GamesViewHolder_ViewBinding.java:33)
at butterknife.internal.DebouncingOnClickListener.onClick(DebouncingOnClickListener.java:22)
at android.view.View.performClick(View.java:5637)
at android.view.View$PerformClick.run(View.java:22429)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

Getting data from fragments on button click in host activity

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?
Note: updated upon clarifications in comments
I would do the following:
In each of the three fragments, implement method getSearchCriteria, with each returning value specific to that fragment.
Implement one OnClickListener for the search button - at the activity level.
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
}
});
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
}
}

What causes a fragment to get detached from an Activity?

I have a SignupActivity which will go through several fragments as users go through a signup process. On the last fragment, I'm calling
getActivity().setResult(Activity.RESULT_OK)
since SingupActivity intent was started for result. Some users are crashing at this point, because getActivity() is producing a NPE. I'm not able to figure out what is causing this. Screen rotation is disabled, so there is no reason that I know of for the fragment to detach from the Activity.
Any insight as to what may be causing this, and how I can resolve it?
public class SignupConfirmationFragment extends Fragment {
public static final String TAG = SignupConfirmationFragment.class.getSimpleName();
private User mNewUser;
private myAppClient mmyAppClient;
private Animation rotateAnimation;
private ImageView avatar;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNewUser = ((SignUpActivity) getActivity()).getNewUser();
mmyAppClient = ((SignUpActivity) getActivity()).getmyAppClient();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.fragment_signup_confirmation, null);
((TextView) v.findViewById(R.id.username_textView)).setText(((SignUpActivity) getActivity()).getNewUser().getName());
avatar = (ImageView) v.findViewById(R.id.avatar);
if (mNewUser.getAvatarImage() != null) {
avatar.setImageBitmap(mNewUser.getAvatarImage());
}
rotateAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.progress_rotate);
v.findViewById(R.id.progress_loading).startAnimation(rotateAnimation);
if (mNewUser.getAvatarImage() != null) {
startAvatarUpload();
} else if (mNewUser.getNewsletter()) {
setNewsletterStatus();
} else {
pauseForOneSecond();
}
return v;
}
private void startAvatarUpload() {
mmyAppClient.uploadUserAvatar(mNewUser.getAvatarImage(), new FutureCallback<JsonObject>() {
#Override
public void onCompleted(Exception e, JsonObject result) {
if (mNewUser.getNewsletter()) {
setNewsletterStatus();
} else {
updateFragment();
}
}
},
null,
null);
}
private void setNewsletterStatus() {
mmyAppClient.setNewsletter(mNewUser.getEmail(), mNewUser.getFirstName(), mNewUser.getLastName(), new FutureCallback<String>() {
#Override
public void onCompleted(Exception e, String result) {
//Log.d(TAG, "Result: " + result);
updateFragment();
}
});
}
private void pauseForOneSecond() {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
updateFragment();
}
}, 1000);
}
private void updateFragment() {
rotateAnimation.cancel();
if (isAdded()) {
getActivity().setResult(Activity.RESULT_OK);
AnalyticsManager.logUIEvent("sign up completed");
getActivity().finish();
} else {
AnalyticsManager.logUIEvent("sign up failed");
}
}
}
According to Fragment lifecycle in Android OS, you cannot get the Activity associated with the fragment in the onCreateView, because the Activity with which the Fragment is associated will not be created at that stage.
See the figure below:
Also, refer to this link, http://developer.android.com/guide/components/fragments.html
As you can see the Activity is created in onActivityCreated which is after onCreateView, hence you'll get null if you try to call the Activity in the onCreateView. Try to call it in onActivityCreated or in onStart that should solve your problem.
I hope this helps.

Categories

Resources