Android Memory Leak When Passing A Fragment - android

I have this code and Iam using the Barcode Google API Vision. When i open the Fragment and rotate the device many times 6 or more, i see in the Dump Heap that many instances remain in memory (see pic.) Even after i do a forced Garbage Collection they stay the same. In my code below i dont see any memory leaks.
Image is after GC
The weird part is that some devices only show 1 instance of the
classes after GC which is normal.
Emulator API 27 : NO MEMORY LEAKS
Samsung j500FN : NO MEMORY LEAKS
Xiaomi mi8 : Memory Leak
Galaxy Tablet E : Memory Leak
MainActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
Fragment sf = getSupportFragmentManager().findFragmentByTag("Scanner");
transaction.add(R.id.root, new Scanner(), "Scanner");
transaction.addToBackStack(null);
transaction.commit();
}
});
}
Scanner
public class Scanner extends Fragment{
public SurfaceView cameraView;
public BarcodeDetector barcode;
public CameraSource cameraSource;
private SurfaceHolder.Callback cameraCallback;
private ActivityScanBinding mbinding;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("ActivityScan","onCreate");
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
Log.d("ActivityScan","onCreateView");
mbinding = DataBindingUtil.inflate(inflater, R.layout.activity_scan, container, false);
mbinding.getRoot().setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
cameraView = mbinding.getRoot().findViewById(R.id.cameraView);
return mbinding.getRoot();
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Scan();
}
#Override
public void onDestroy() {
Log.d("ActivityScan","Destroyed");
if(barcode!=null) {
barcode.release();
Log.d("barcode","Released");
}
if(cameraSource!=null) {
cameraSource.release();
Log.d("cameraSource ","Released");
}
if(cameraView!=null) {
removeCameraViewCallback();
}
super.onDestroy();
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
}
public void Scan(){
cameraView.setZOrderMediaOverlay(true);
barcode = new BarcodeDetector.Builder(getActivity())
.setBarcodeFormats(Barcode.QR_CODE)
.build();
if(!barcode.isOperational()){
return;
}
cameraSource = new CameraSource.Builder(getActivity(), barcode)
.setFacing(CameraSource.CAMERA_FACING_FRONT)
.setRequestedFps(24)
.setAutoFocusEnabled(true)
.setRequestedPreviewSize(1920,1080)
.build();
cameraCallback = new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
if(ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){
cameraSource.start(cameraView.getHolder());
}
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
cameraSource.stop();
}
};
cameraView.getHolder().addCallback(cameraCallback);
barcode.setProcessor(new Detector.Processor<Barcode>() {
#Override
public void release() {}
#Override
public void receiveDetections(Detector.Detections<Barcode> detections) {
final SparseArray<Barcode> barcodes = detections.getDetectedItems();
if(barcodes.size() > 0){
}
}
});
}
public void removeCameraViewCallback(){
cameraView.getHolder().removeCallback(cameraCallback);
}
}
Please see my code and let me know if there is a memory leak.
Leak Canary shows this:

Why you're releasing barcode and cameraSource in onDestroy method?
According to this onDestroy() method could be skipped and not called. Maybe onStop() is more proper place to release resources? And acquire them in onStart() respectively.
#Override
public void onStop() {
Log.d("ActivityScan","Destroyed");
if(barcode!=null) {
barcode.release();
Log.d("barcode","Released");
}
if(cameraSource!=null) {
cameraSource.release();
Log.d("cameraSource ","Released");
}
if(cameraView!=null) {
removeCameraViewCallback();
}
super.onStop();
}
Also, do not pass Activity when creating BarcodeDetector and CameraSource, and pass ApplicationContext if possible.

Related

Use YoutubePlayerView on fragment section

I found some similar situation, like this and this, but none of then solved my problem.
I have wanna have a screen with youtube video and some text information like this:
I'm using a activity and a fragment, both with base class that extended YouTubeBaseActivity and YouTubePlayerFragment.
But when I'm trying to open the fragment it show a npe but it don't show where. Since I'm getting the layout and view right, I don't now what is going on.
Hope that it don't get downvotes because is a npe question, but is different from usual, this API call don't show me where the NPE happen and I saw the other people has problems like this
Obs: I'm using this extend base concept because more places will have this videos behaviour and I'm trying to avoid code repeat.
Logcat
XML
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".screens.mine.fragments.MineStepsFragment">
<com.google.android.youtube.player.YouTubePlayerView
android:id="#+id/mine_steps_youtube_player"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#color/background_white"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
</com.google.android.youtube.player.YouTubePlayerView>
</android.support.constraint.ConstraintLayout>
Activity
public class MineAccidentActivity extends BaseYoutubeActivity {
#Override
protected void initializeActionBar() {
actionbarLeftBtn.setVisibility(View.VISIBLE);
actionbarTitle.setVisibility(View.VISIBLE);
}
#Override
protected int getActionbarTitle() {
return R.string.mine_accident;
}
#Override
protected int getContentView() {
return R.layout.mine_accident_activity;
}
#Override
protected void assignViews() {
MineAccidentController.getInstance().attachToActivity(this, R.id.mine_container);
}
#Override
protected void prepareViews() {}
/////////////////// BACK ////////////////////
#Override
public void onBackPressed() {
if (!MineAccidentController.getInstance().isFirstFragmentShown()) {
MineAccidentController.getInstance().showPreviousFragment();
} else {
super.onBackPressed();
}
}
/////////////////// LIFE CYCLE ////////////////////
#Override
protected void onDestroy() {
MineAccidentController.getInstance().onDestroy();
super.onDestroy();
}
}
Fragment Controller
public class MineAccidentController extends BaseYoutubeController {
private String accidentType;
#Override
protected ArrayList<android.app.Fragment> initFragments() {
ArrayList<android.app.Fragment> fragments = new ArrayList<>();
fragments.add(new MineStepsFragment());
return fragments;
}
//create Class
public static MineAccidentController getInstance() {
if (null == instance) {
synchronized (MineAccidentController.class) {
if (null == instance) {
setInstance(new MineAccidentController());
}
}
}
return (MineAccidentController) instance;
}
public String getAccidentType() {
return accidentType;
}
public void setAccidentType(String accidentType) {
this.accidentType = accidentType;
}
}
Fragment
public class MineStepsFragment extends BaseYoutubeFragment {
//Not in Layout
private String videoUrl;
////////////// IMPLEMENT_METHODS //////////////
#Override
protected int getFragmentContentView() {
return R.layout.mine_steps_fragment;
}
#Override
protected int getYoutubePlayerView() {
return R.id.mine_steps_youtube_player;
}
#Override
protected void assignViews() {
}
#Override
protected void prepareViews() {
}
#Override
public void onInitializationSuccess(YouTubePlayer.Provider provider, YouTubePlayer youTubePlayer, boolean wasRestored) {
if(!wasRestored){
checkType();
youTubePlayer.cueVideo(videoUrl);
}
}
#Override
public void onInitializationFailure(YouTubePlayer.Provider provider, YouTubeInitializationResult youTubeInitializationResult) {
showErrorToast(getActivity(), R.string.error_initialize_video);
}
////////////// FUNCTIONS //////////////
private void checkType() {
if(MineAccidentController.getInstance().getAccidentType().equals(Parameters.ACCIDENT_PERSONAL)){
videoUrl = Properties.MINE_PERSONAL_VIDEO;
}
else {
videoUrl = Properties.MINE_WORK_VIDEO;
}
}
}
Base Fragment
public abstract class BaseYoutubeFragment extends YouTubePlayerFragment implements YouTubePlayer.OnInitializedListener {
protected View fragmentView = null;
protected YouTubePlayerView youTubePlayerView;
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
assignViews();
prepareViews();
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
fragmentView = inflater.inflate(getFragmentContentView(), container, false);
youTubePlayerView = fragmentView.findViewById(getYoutubePlayerView());
youTubePlayerView.initialize(com.can_apps.eva_ngo.properties.Properties.API_KEY, this);
return fragmentView;
}
/////////////////// ABSTRACT METHODS ////////////////////
protected abstract int getFragmentContentView(); //Get Layout R.layout.name_file
protected abstract int getYoutubePlayerView(); //Get Container for YOutube Video
protected abstract void assignViews(); //Used for findById the params
protected abstract void prepareViews(); //Used for start the values of params
/////////////////// SHOW MESSAGES ////////////////////
public void showErrorToast(Context context, final int message) {
Toast toast = Toast.makeText(context, getString(message), Toast.LENGTH_SHORT);
View view = toast.getView();
view.setBackgroundResource(R.color.background_red_transparent);
toast.show();
}
/////////////////// SNACK BAR ////////////////////
public void showSnackBar(final int text) {
Snackbar.make(Objects.requireNonNull(getView()), getString(text), Snackbar.LENGTH_SHORT).show();
}
public void showSnackBar(final int mainTextStringId, final int actionStringId, View.OnClickListener listener) {
Snackbar.make(Objects.requireNonNull(getView()),
getString(mainTextStringId),
Snackbar.LENGTH_LONG)
.setAction(getString(actionStringId), listener).show();
}
}
Looking at the logcat it looks like your YouTubePlayerView is null when you are calling one of its methods.
The YouTube Player API is quite buggy and difficult to use correctly. To solve this problems (and others) I have built an alternative player Android-YouTube-Player, it's open source and you can do whatever you want with it.
In your case, you won't have to meddle with Fragments and transactions, since my YouTubePlayerView is just a regular view and requires no special Fragments or Activities. You can drop it wherever you want.
Hope it could be useful to you as well!

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

Maintaining list items positions on device rotation in an android fragment

In a fragment I am trying to save the scroll state of the RecyclerView list, but somehow it is not saving the state. As it is a fragment, I am overriding the onSaveInstanceState() and onActivityCreated() methods to save the scroll position. Even tried implementing in onViewStateRestored() method. I saw related some posts on saving the scroll state but it ain't working. Kindly let me know where am I failing. Below is my code:
public class RecipeListFragment extends Fragment
implements RecipeListContract.View {
#BindView(R.id.recipe_list_recycler_view)
RecyclerView mRecipeListRecyclerView;
#BindView(R.id.recipe_list_progress_bar)
ProgressBar mRecipeListProgressBar;
#BindInt(R.integer.grid_column_count)
int mGridColumnCount;
#BindString(R.string.recipe_list_sync_completed)
String mRecipeListSyncCompleted;
#BindString(R.string.recipe_list_connection_error)
String mRecipeListConnectionError;
GridLayoutManager gridLayoutManager;
Parcelable savedRecyclerLayoutState;
Unbinder unbinder;
private static final String SAVED_LAYOUT_MANAGER
= "com.learnwithme.buildapps.bakingapp.ui.recipelist.fragment";
private RecipeListContract.Presenter mRecipeListPresenter;
private RecipeListAdapter mRecipeListAdapter;
public RecipeListFragment() { }
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup
container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_recipe_list, container,
false);
unbinder = ButterKnife.bind(this, view);
mRecipeListAdapter = new RecipeListAdapter(
getContext(),
new ArrayList<>(0),
recipeId -> mRecipeListPresenter.loadRecipeDetails(recipeId)
);
mRecipeListAdapter.setHasStableIds(true);
gridLayoutManager = new GridLayoutManager(getContext(),
mGridColumnCount);
mRecipeListRecyclerView.setLayoutManager(gridLayoutManager);
mRecipeListRecyclerView.setHasFixedSize(true);
mRecipeListRecyclerView.setAdapter(mRecipeListAdapter);
return view;
}
#Override
public void onPause() {
super.onPause();
mRecipeListPresenter.unsubscribe();
}
#Override
public void onResume() {
super.onResume();
mRecipeListPresenter.subscribe();
}
#Override
public void onSaveInstanceState() { }
#Override
public void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
if(bundle != null) {
bundle.putParcelable(SAVED_LAYOUT_MANAGER,
mRecipeListRecyclerView
.getLayoutManager()
.onSaveInstanceState());
Timber.d("instance state=>",
mRecipeListRecyclerView.getLayoutManager().onSaveInstanceState());
}
}
#Override
public void onViewStateRestored(#Nullable Bundle bundle) {
super.onViewStateRestored(bundle);
if(bundle != null) {
savedRecyclerLayoutState =
bundle.getParcelable(SAVED_LAYOUT_MANAGER);
Timber.d("onViewStateRestored savedRecyclerLayoutState=>",
savedRecyclerLayoutState);
mRecipeListRecyclerView
.getLayoutManager()
.onRestoreInstanceState(savedRecyclerLayoutState);
}
}
#Override
public void onActivityCreated(#Nullable Bundle bundle) {
super.onActivityCreated(bundle);
if(bundle != null) {
savedRecyclerLayoutState =
bundle.getParcelable(SAVED_LAYOUT_MANAGER);
Timber.d("onViewStateRestored savedRecyclerLayoutState=>",
savedRecyclerLayoutState);
mRecipeListRecyclerView
.getLayoutManager()
.onRestoreInstanceState(savedRecyclerLayoutState);
}
}
#Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
public static RecipeListFragment newInstance() {
return new RecipeListFragment();
}
#Override
public void setPresenter(RecipeListContract.Presenter recipeListPresenter) {
this.mRecipeListPresenter = recipeListPresenter;
}
#Override
public void showRecipeList(List<Recipe> recipeList) {
mRecipeListAdapter.refreshRecipes(recipeList);
}
#Override
public void loadProgressBar(boolean show) {
setViewVisibility(mRecipeListRecyclerView, !show);
setViewVisibility(mRecipeListProgressBar, show);
}
#Override
public void displayCompletedMessage() {
Toast.makeText(getContext(), mRecipeListSyncCompleted,
Toast.LENGTH_SHORT).show();
}
#Override
public void displayErrorMessage() {
Toast.makeText(getContext(), mRecipeListConnectionError,
Toast.LENGTH_SHORT).show();
}
#Override
public void displayRecipeDetails(int recipeId) {
startActivity(RecipeDetailsActivity.prepareIntent(getContext(),
recipeId));
}
private void setViewVisibility(View view, boolean visible) {
if (visible) {
view.setVisibility(View.VISIBLE);
} else {
view.setVisibility(View.INVISIBLE);
}
}
}
I have resolved the issue myself. The problem was to save the scroll position of the device in onSaveInstanceState() and restoring the same in onViewRestored() method.
private Parcelable mRecipeListParcelable;
private int mScrollPosition = -1;
#Override
public void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
int scrollPosition = ((GridLayoutManager)
mRecipeListRecyclerView.getLayoutManager())
.findFirstCompletelyVisibleItemPosition();
mRecipeListParcelable = gridLayoutManager.onSaveInstanceState();
bundle.putParcelable(KEY_LAYOUT, mRecipeListParcelable);
bundle.putInt(POSITION, scrollPosition);
}
#Override
public void onViewStateRestored(#Nullable Bundle bundle) {
super.onViewStateRestored(bundle);
if(bundle != null) {
mRecipeListParcelable = bundle.getParcelable(KEY_LAYOUT);
mScrollPosition = bundle.getInt(POSITION);
}
}
Also, in the loadProgress() method I had to set the scrollToPosition() with the scroll position saved.
#Override
public void loadProgressBar(boolean show) {
setViewVisibility(mRecipeListRecyclerView, !show);
setViewVisibility(mRecipeListProgressBar, show);
mRecipeListRecyclerView.scrollToPosition(mScrollPosition);
}
Also, one more thing to remember is that no need to restore anything in onResume() method as the presenter callbacks would get called and the view is reset anyway.

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.

Android with fragments, duplicated elements

I am building an Android Application (minimum SDK Level 10). When the app is minimized and the user return later to it, the main menu shows buttons and images twice. It only happens when the user uses other apps in the middle, if it happens immediately then the menu is displayed correctly.
Here is the code of the fragment:
public class MainMenuFragment extends Fragment implements OnClickListener {
String mGreeting = "Hello, anonymous user (not signed in)";
public interface Listener {
public void onStartGameRequested(boolean hardMode);
public void onShowAchievementsRequested();
public void onShowLeaderboardsRequested();
public void onSignInButtonClicked();
public void onSignOutButtonClicked();
public void onPlayClicked();
public void onScoreClicked();
public void onFeedbackClicked();
}
Listener mListener = null;
boolean mShowSignIn = true;
View v;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if(v==null){
v = inflater.inflate(R.layout.fragment_mainmenu, container, false);
final int[] CLICKABLES = new int[] {
R.id.show_leaderboards_button,
R.id.sign_in_button, R.id.sign_out_button, R.id.button_play, R.id.feedback
};
for (int i : CLICKABLES) {
v.findViewById(i).setOnClickListener(this);
}
}
else {
((ViewGroup)v.getParent()).removeView(v);
}
return v;
}
public void setListener(Listener l) {
mListener = l;
}
#Override
public void onStart() {
super.onStart();
//updateUi();
}
#Override
public void onResume() {
super.onResume();
updateUi();
}
#Override
public void onStop() {
super.onStop();
}
public void setGreeting(String greeting) {
mGreeting = greeting;
//updateUi();
}
void updateUi() {
try{
if (getActivity() == null) return;
getActivity().findViewById(R.id.sign_in_button).setVisibility(mShowSignIn ?
View.VISIBLE : View.GONE);
getActivity().findViewById(R.id.sign_out_button).setVisibility(mShowSignIn ?
View.GONE : View.VISIBLE);
}catch(Exception e){}
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.show_leaderboards_button:
mListener.onShowLeaderboardsRequested();
break;
case R.id.sign_in_button:
mListener.onSignInButtonClicked();
break;
case R.id.sign_out_button:
mListener.onSignOutButtonClicked();
break;
case R.id.feedback:
mListener.onFeedbackClicked();
break;
case R.id.button_play:
mListener.onPlayClicked();
break;
}
}
public void setShowSignInButton(boolean showSignIn) {
mShowSignIn = showSignIn;
updateUi();
}
}
Thank you in advance for any help. I cannot see what is called twice, and I have spent more hours than I would to admit.
EDIT 1: Trying Clint Deygoo solution. Defining View v inside the onCreate is not working neither. This is how I did it:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_mainmenu, container, false);
final int[] CLICKABLES = new int[] {
R.id.show_leaderboards_button,
R.id.sign_in_button, R.id.sign_out_button, R.id.button_play, R.id.feedback
};
for (int i : CLICKABLES) {
v.findViewById(i).setOnClickListener(this);
}
return v;
}
EDIT 2: SOLVED ISSUE. I removed updateUi() and it stopped happening. I am not sure about why it's working, but it is. Thank you all!
The problem is not in your onCreateView (You should use EDIT 1). It's about nested Fragments.
Raneez Ahmed posted a good answer here
Fragments within Fragments

Categories

Resources