I am working on ViewPager and using Fragment there I found
setUserVisibleHint() called before onCreateView() in Fragment
I am using Fragment from support library android.support.v4.app.Fragment
Is this is a problem with Library ?
How can I get rid of it ?
EDIT
I Override setUserVisibleHint() and not calling super to get rid of it.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
//FIXED: setUserVisibleHint() called before onCreateView() in Fragment causes NullPointerException
//super.setUserVisibleHint(isVisibleToUser);
}
// create boolean for fetching data
private boolean isViewShown = false;
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getView() != null) {
isViewShown = true;
// fetchdata() contains logic to show data when page is selected mostly asynctask to fill the data
fetchData();
} else {
isViewShown = false;
}
}
Use isViewShown instance variable to decide whether to fetch data in onCreateView() or in setUserVisibleHint().
Below code contains logic for onCreateView():
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.main_layout, container, false);
// view initialization steps.......
if (!isViewShown) {
fetchData();
}
// do other stuff
}
This code will solve your problem. As It solved my problem. :)
This trick will fetch data in onCreateView() for direct jumping from one page to another, whereas when you swipe the view it will fetch the data from setUserVisibleHint() method. :)
you can use this logic, also you can turn off viewDidAppear any time by setting isVisible = false
public class MyFragment extends Fragment {
private Boolean isStarted = false;
private Boolean isVisible = false;
#Override
public void onStart() {
super.onStart();
isStarted = true;
if (isVisible && isStarted){
viewDidAppear();
}
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
isVisible = isVisibleToUser;
if (isStarted && isVisible) {
viewDidAppear();
}
}
public void viewDidAppear() {
// your logic
}
}
I found the best solution
private boolean isVisible;
private boolean isStarted;
#Override
public void onStart() {
super.onStart();
isStarted = true;
if (isVisible)
sendRequest(); //your request method
}
#Override
public void onStop() {
super.onStop();
isStarted = false;
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
isVisible = isVisibleToUser;
if (isVisible && isStarted)
sendRequest(); //your request method
}
It's improved version of fareed namrouti's answer. I tested this on many conditions. It's safe.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, container, false);
if (getUserVisibleHint()) {
sendRequest();
}
return view;
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
if (isResumed()){
sendRequest();
}
}
}
Below Worked for me....
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
//// create class member variable to store view
viewFrag =inflater.inflate(R.layout.fragment_main_favorite, container, false);
// Inflate the layout for this fragment
return viewFrag;
}
and use this
#Override
public void setUserVisibleHint(boolean visible)
{
super.setUserVisibleHint(visible);
if (visible)
{
View v = viewFrag ;
if (v == null) {
Toast.makeText(getActivity(), "ERROR ", Toast.LENGTH_LONG ).show();
return;
}
}
}
My SightFragment.java here, should reset the flags in onDestroyView():
package cc.cubone.turbo.core.app;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.View;
/**
* Fragment for handling view after it has been created and visible to user for the first time.
*
* <p>Specially in {#link android.support.v4.view.ViewPager}, the page will be created beforehand
* but not be visible to user.
*
* <p>Call {#link android.support.v4.view.ViewPager#setOffscreenPageLimit(int)} to set the number of
* pages that should be retained.
*
* Reference:
* <ul>
* <li><a href="http://stackoverflow.com/questions/10024739/how-to-determine-when-fragment-becomes-visible-in-viewpager">
* How to determine when Fragment becomes visible in ViewPager</a>
* </ul>
*/
public class SightFragment extends Fragment {
private boolean mUserSeen = false;
private boolean mViewCreated = false;
public SightFragment() {
}
/*public boolean isUserSeen() {
return mUserSeen;
}
public boolean isViewCreated() {
return mViewCreated;
}*/
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (!mUserSeen && isVisibleToUser) {
mUserSeen = true;
onUserFirstSight();
tryViewCreatedFirstSight();
}
onUserVisibleChanged(isVisibleToUser);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
// Override this if you want to get savedInstanceState.
mViewCreated = true;
tryViewCreatedFirstSight();
}
#Override
public void onDestroyView() {
super.onDestroyView();
mViewCreated = false;
mUserSeen = false;
}
private void tryViewCreatedFirstSight() {
if (mUserSeen && mViewCreated) {
onViewCreatedFirstSight(getView());
}
}
/**
* Called when the new created view is visible to user for the first time.
*/
protected void onViewCreatedFirstSight(View view) {
// handling here
}
/**
* Called when the fragment's UI is visible to user for the first time.
*
* <p>However, the view may not be created currently if in {#link android.support.v4.view.ViewPager}.
*/
protected void onUserFirstSight() {
}
/**
* Called when the visible state to user has been changed.
*/
protected void onUserVisibleChanged(boolean visible) {
}
}
While most of this solutions work, you don't even need to track the state by yourself.
With current versions fo the support library there is a isResumed() method which does probably what most of you try to achieve by using an isStarted flag:
Return true if the fragment is in the resumed state. This is true for the duration of onResume() and onPause() as well.
And then it's as easy as:
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isResumed()) {
updateUi(isVisibleToUser);
}
}
That simple variant work in my code:
#Override
public void onStart() {
super.onStart();
if (getUserVisibleHint()) {
updateUI(); // your logic
}
}
and
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isResumed() && isVisibleToUser) {
updateUI(); // your logic
}
}
Create this code in your setUserVisibleHint() :
if(isVisibleToUser && getView() != null){
isActive = true;
init();
}else if(isVisibleToUser && getView() == null){
isActive = false;
}else{
isActive = true;
}
In your onCreateView() :
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
if(!isActive){
init();
}
}
BELOW WORKED FOR ME
Please create a global view like this
private View view;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
//Inflate view layout
view =inflater.inflate(R.layout.your_fragment, container, false);
// return view
return view;
}
and use this
#Override
public void setUserVisibleHint(boolean isUserVisible)
{
super.setUserVisibleHint(isUserVisible);
//When fragment is visible to user and view is not null then enter here.
if (isUserVisible && view != null)
{
// do your stuff here.
}
}
The behavior that you are experiencing is specified in the documentation itself.
Note: This method may be called outside of the fragment lifecycle and thus has no ordering guarantees with regard to fragment lifecycle method calls.
Check it here.
https://developer.android.com/reference/android/app/Fragment#setUserVisibleHint(boolean)
Now, You no longer required to use this Method for FragmentPagerAdapter.
It use send true for onView Screen and false for non-View Screen, while called for ViewPager Everytime.
Since Android have release LifeCycle in AndroidX, all lifecycle methods are called for visible screen. LifeCycle dont run after onCreateView for Non-Visible Screens in PagerAdapter.
This is for:
~Depricated setuservisiblehint
is hard to maintain and listen to a state, I use the hacky method to listen to visibility
For AndroidX Fragment
Initially, I put every show transaction, I added
fragment.userVisibleHint = true
currentFragment?.userVisibleHint = false
transaction.add(fragmentContainer.id, fragment, "tag").commit()
For hide/remove transactions, I added
currentFragment.userVisibleHint = false
In My Fragment/BaseFragment:
class ExampleFragment:Fragment(){
val visibilityStateChanges = MutableLiveData<Boolean>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
visibilityStateChanges.value = true
//return your view
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
Log.e("JeyK", "isVisibleToUser $isVisibleToUser")
visibilityStateChanges.value = isVisibleToUser
}
}
}
in your Fragment, you could observe the liveData to receive visibility states
visibilityStateChanges.observe(this#MapPathFinderFragment) {
Log.e("visibilityStateChanges", "Visibility changed $it")
}
This is the best solution i found.
#Override
public void onCreateView() {
super.onStart();
if (getUserVisibilityHint()){
//do stuff
}
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isResumed() && isVisibleToUser) {
//do stuff
}
}
Related
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.
Because All fragment loaded with together In Pager Sliding tab , I need to use setUserVisibleHint() for laoding that fragment selected .
I have Problem with Context in the setUserVisibleHint() method :
It raised NPE Exception when I use getActivity for my Context. Thanks
Because getActivity will return null before fragment attached to Activity you need check this value will null or check fragment attached to activity or after onActivityActtached function.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getActivity() != null) {
//do something
}
}
//or
boolean isAttached = false;
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
isAttached = true;
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isAttached) {
//do something
}
}
Its better to use newInstance Method and put your String over there. And then in fragment use
getArguments().getString("KEY")
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.
Load fragment when user stops swiping instead of previous,current,next pattern in fragmentviewpager in android?
Load single fragment at a time in fragmentviewpager instead of multiple.
On Fragment Load FragmentViewPager sets setUserVisibleHint to true of current fragment. Through that we can put delay in loading fragment in FragmentViewPager. In Below code i have put delay of 1000 ms.
#Override
public boolean getUserVisibleHint() {
boolean isVisible = super.getUserVisibleHint();
return isVisible;
}
public Runnable handleDelays = new Runnable() {
#Override
public void run() {
if (getUserVisibleHint()) {
// Application Logic
}
}
};
private Handler pagerHandler;
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (this.isVisible()) {
if (!isVisibleToUser) {
pagerHandler.removeCallbacks(handleDelays);
} else {
pagerHandler.removeCallbacks(handleDelays);
pagerHandler.postDelayed(handleDelays, 1000);
}
} else {
if (pagerHandler != null) {
if (!isVisibleToUser) {
pagerHandler.removeCallbacks(handleDelays);
} else {
pagerHandler.removeCallbacks(handleDelays);
pagerHandler.postDelayed(handleDelays, 1000);
}
} else {
pagerHandler = new Handler();
}
}
}
I am trying to save fragment state. I have an activity and several fragments. The sequence of actions: add first fragment, change view manually (make visibility of first LinearLayout GONE and second LinearLayout VISIBLE), detach fragment, add another one, detach it and again attach first fragment.
Adding/attaching/detaching works good but setRetainInstanse(true) saves only initial fragment state.
Finally I get first LinearLayout visible at my fragment (instead of second) so I've tried to make it by hands but it doesn't work:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(BUNDLE_IS_LOADING)) {
if (savedInstanceState.getBoolean(BUNDLE_IS_LOADING)) {
mBlockContent.setVisibility(View.GONE);
mBlockProgress.setVisibility(View.VISIBLE);
} else {
mBlockContent.setVisibility(View.VISIBLE);
mBlockProgress.setVisibility(View.GONE);
}
}
}
}
setRetainInstance(true);
}
#Override
public void onSaveInstanceState(Bundle b) {
super.onSaveInstanceState(b);
b.putBoolean(BUNDLE_IS_LOADING,
mBlockProgress.getVisibility() == View.VISIBLE);
}
I use compatibility library rev. 11.
Solution for me:
private boolean isProgressing;
private void saveViewsState() {
isProgressing = mBlockProgress.getVisibility() == View.VISIBLE;
}
private void switchToProgress() {
mBlockContent.setVisibility(View.GONE);
mBlockProgress.setVisibility(View.VISIBLE);
}
private void switchToContent() {
mBlockContent.setVisibility(View.VISIBLE);
mBlockProgress.setVisibility(View.GONE);
}
#Override
public void onSaveInstanceState(Bundle b) {
super.onSaveInstanceState(b);
saveViewsState();
}
#Override
public void onPause() {
super.onPause();
saveViewsState();
}
#Override
public void onResume() {
super.onResume();
if (isProgressing) {
switchToProgress();
} else {
switchToContent();
}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (isProgressing) {
switchToProgress();
} else {
switchToContent();
}
}