I have fragment which includes a viewpager which includes many fragments. I am calling methods of fragments which are in viewpager from outer fragment. I can do this without any problem but I am facing an issue, when I go to another fragment from fragments which are in viewpager and then I come back, I cannot use that method because method-calling object viewed as null.
Problem phases in order
layout structure
1- In outer fragment, a method of one of viewpager's fragment is called without anyproblem.
2- Click on anyitem in recycler view in fragments which are settled in viewpager.
3- It directs user to another fragment.
4- Go back via popbackstackimmediate().
5- First phase causes problem with null pointer exception.
This is the method implemented in outer fragment.
private void setPagination() {
nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (v.getChildAt(v.getChildCount() - 1) != null) {
if ((scrollY >= (v.getChildAt(v.getChildCount() - 1).getMeasuredHeight() - v.getMeasuredHeight())) &&
scrollY > oldScrollY) {
if (currentPage.equalsIgnoreCase("A"))
AFragment.loadMore();
else if (currentPage.equalsIgnoreCase("B"))
BFragment.loadMore();
else if (currentPage.equalsIgnoreCase("C"))
CFragment.loadMore();
else if (currentPage.equalsIgnoreCase("D"))
DFragment.loadMore();
}
}
}
});
}
LoadMore() method
public void loadMore() {
if (list_a.size() % 4 == 0) {
ManagerAll.getInstance().fetchA(id_number, page).enqueue(new Callback<List<A>>() {
#Override
public void onResponse(Call<List<A>> call, Response<List<A>> response) {
if (response.isSuccessful()) {
int before_update = list_a.size();
list_a.addAll(response.body());
int after_update = list_a.size();
adapter.notifyItemRangeInserted(before_update, after_update);
page++;
}
}
#Override
public void onFailure(Call<List<A>> call, Throwable t) {
}
});
}
}
I cannot access list_a here because it throws null pointer exception. I guess it is not initialized yet after I come back from another fragment.
Related
My question is quite similar to this one: fragment.getView() return null after backpressed
The problem is next: I have a special Fragment with two states: A and B. If Fragment is in the state B, Backpress should switch the state from B to A. (The difference between two states is in visibility of some elements on a layout) If Fragment is in state A, Backpress should close this Fragment. I overwrote the onBackPressed() method in my activity in next way:
#Override
public void onBackPressed() {
int count = getSupportFragmentManager().getBackStackEntryCount();
if (count > 1) {
if (isStateB) {
isStateB = false;
MessagesFragment messFragment = ((MessagesFragment) getSupportFragmentManager().findFragmentByTag("MessagesFragment"));
if (messFragment != null) {
Log.d(TAG, "Message Fragment Refresh");
messFragment.refreshFragWithoutMessChecked();
return;
} else {
Log.d(TAG, "Fail To Message Fragment Resfresh");
}
}
}
}
super.onBackPressed();
}
This code executes refreshFragWithoutMessChecked() but nothing happens. All elements from the layout which I am trying to close aren't null, but code doesn't affect them. Also I have a button in the MessageFragment which executes the similar method, and in case I press it, the code works well. In additional I found out that if I call getView() inside refreshFragWithoutMessChecked(). In case when it called from onBackPressed(), getView() returns NULL. In case when it called from onClick() it returns something isn't NULL.
So that is why I am asking, why Backpress makes my getView() returns NULL, and how can I solve my problem?
Fragment Code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.frag_messages, container, false);
/*
Here is a big amount of elements initizalizations and onClick bindings like this:
check_message = view.findViewById(R.id.check_message);
check_message.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {MainActivity.isStateB = true;}});
*/
return view;
}
public void refreshFragWithoutMessChecked() {
MainActivity.isStateB = false;
MainActivity.posMessChecksEnabled.clear();
refreshMessageFragment();
Log.d(TAG,"Message Fragment Refreshed");
}
// Refresh Message Fragment
public void refreshMessageFragment() {
if(getView() != null){
Log.d(TAG,"A");
} else {
Log.d(TAG,"B");
}
// Hide Message Check Show
// THIS CODE DOESN'T WORK ON BACK PRESS BUT IT'S EXECUTED
if (MainActivity.isStateB == true) {
((MainActivity) getActivity()).getSupportActionBar().hide();
write_message_layout.setVisibility(View.GONE);
reply_forward_bottom.setVisibility(View.VISIBLE);
up_message_selected_panel.setVisibility(View.VISIBLE);
toolbar.setVisibility(View.GONE);
appbar_layout.setVisibility(View.GONE);
} else {
if (fromOpen.equalsIgnoreCase(MainActivity.MESSAGES_FILTER_CONTACTS) == false) {
write_message_layout.setVisibility(View.VISIBLE);
}
reply_forward_bottom.setVisibility(View.GONE);
up_message_selected_panel.setVisibility(View.GONE);
toolbar.setVisibility(View.VISIBLE);
appbar_layout.setVisibility(View.VISIBLE);
}
}
I have a back stack of a few fragments and I need to know that I have returned to one from another.
Similar to when you have onActivityResult I was wondering if you could have a check for popBackStack() calls in a fragment?
I call it in another fragment here:
#Override
public void postFinished(){
getFragmentManager().popBackStack();
}
Anything like this in android java?
You Can add Listener for BackStackChange on your Activity page Like,
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// Your Code Here
}
});
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager fm = getSupportFragmentManager();
if (fm != null) {
int backStackCount = fm.getBackStackEntryCount();
if (backStackCount == 0) {
}
}
}
});
You can use addOnBackStackChangedListener with fm.getBackStackEntryCount(); which will give back stack count of fragments.
you can do in onViewCreated() method:
if (savedInstanceState == null && !mAlreadyLoaded) {
mAlreadyLoaded = true;
// Do this code only first time, not after rotation or reuse fragment from backstack
}
Because when android put fragment on backstack, it only destroy its view, but don't kill instance itself, so mAlreadyLoaded will be still true when fragment will be restored from backstack.
I have a RealmRecyclerViewAdapter that listens to a query in realm, and I've Implemented a drag and drop functionality on it using the ItemTouchHelper.
private final ItemTouchHelper.Callback _ithCallback = new ItemTouchHelper.Callback() {
private int fromPosition;
private int toPosition;
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
if (viewHolder.getItemViewType() != target.getItemViewType()) {
return false;
}
toPosition = target.getAdapterPosition();
adapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
if (viewHolder.getItemViewType() == TaskListRecyclerViewAdapter.FOOTER) {
return 0;
} else {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
}
}
#Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
fromPosition = viewHolder.getAdapterPosition();
toPosition = viewHolder.getAdapterPosition();
} else if (actionState == ItemTouchHelper.ACTION_STATE_IDLE
&& fromPosition != toPosition) {
// adapter.onDetachedFromRecyclerView(mRecyclerView);
dragAndDropManager.executeDragAndDrop(liveRealm, store.getStoreUid(), fromPosition, toPosition);
...
...
// adapter.onAttachedToRecyclerView(mRecyclerView);
}
}
};
To indicate the movement, whenever I drag an item over another Item, I call the adapter's notifyItemMoved method:
adapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
and whenever the user releases the item he dragged, I commit the changes to the realm DB:
dragAndDropManager.executeDragAndDrop(liveRealm, store.getStoreUid(), fromPosition, toPosition);
the problem is - whenever I release the item I've dragged, the animation appears to be working as if I haven't dropped the item in its location, but as if the item starts moving from its original location to the location in which I've dropped it.
I understand that this happens because I've committed the changes to Realm and as such the change is notified in the adapter, but the animation looks buggy.
I've tried calling
adapter.onDetachedFromRecyclerView(mRecyclerView);
and
adapter.onAttachedToRecyclerView(mRecyclerView);
to remove and add the listener to the adapter but it seems to be unreliable.
Is there a better solution for this issue?
Michael - did you ever get this working to your satisfaction?
I just ran across your post, having the same issue. I'm using the local Java DB version of Realm and after reviewing several examples that show similar techniques for implementing drag and drop with the stock recyclerView (generally they are all using an array of strings as the data model, NOT something like a live DB such as Realm) here is my experience - what I saw as issues with the "standard" techniques and how I resolved them:
Issues To Resolve
Issue 1: Custom Callback class forwards large numbers of onMove events
The various examples I found (like this one here) always utilize a
custom class that extends ItemTouchHelper.Callback. This classes typically defines
an interface that is in turn implemented by the adapter. Then this class overrides
onMove()/onSwiped from the Callback to call the onViewMoved()/onViewSwiped() method of the interface implemented by the adapter:
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
contract.onViewMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
In the onViewMoved() implementation that is in the adapter, the examples typically update the data source, then call notifyItemMoved():
public void onViewMoved(int oldPosition, int newPosition) {
User targetUser = usersList.get(oldPosition);
User user = new User(targetUser);
usersList.remove(oldPosition);
usersList.add(newPosition, user);
notifyItemMoved(oldPosition, newPosition);
}
In the example above, the data model is a simple ArrayList of "User" objects.
The problem I found with this when using Realm is that in the Callback onMove() gets called a lot as the user drags, which
given what is happening in the standard interface implementation would cause a huge amount of DB
writes.
Issue 2: The drag animation stops as soon as a single row change is detected
I noticed that as I was dragging the top row of the recyclerview down, as soon as it reached the very next row the animation stopped. Why? It is because as you found out the base class RealmRecyclerViewAdapter automatically updates itself when it gets notifications that the data model has changed, and this update apparently triggers the ItemTouchHelper to stop the animation.
To solve these two issues, I did the following:
Like the examples I found, I extended ItemTouchHelper.Callback into my own class. I utilize some variables to keep track of whether I have dragged over a new target or not:
public class RecyclerViewSwipeAndDragHelper extends ItemTouchHelper.Callback {
private SwipeAndDraggable implementor;
private static final int POSITION_UNKNOWN = -1;
private int oldPosition = POSITION_UNKNOWN;
private int newPosition = POSITION_UNKNOWN;
And similar to the example, in this class I define the interface that will be used by my adapter:
public interface SwipeAndDraggable {
void onViewMoved(int oldPosition, int newPosition);
void onViewSwiped(int position);
void onClearView(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder);
}
I added the onClearView() method in this interface to what is typically shown in the examples, more on how that is used in a moment.
To solve the first issue, I needed to consolidate all the onMove() callbacks into only a single call into my adapter. Fortunately, ItemTouchHelper already does a lot of this work for us. A return value of TRUE from onMove() will trigger a follow-up call to onMoved(). Returning FALSE does not. So, in my Callback implementation when I override onMove(), I don't call the implementer of the interface. Instead, I only keep track of whether or not I've dragged over the same target position, so that it only returns TRUE the first time I hit a new target (and I also noticed through testing the sometimes I would get an initial -1 value for viewHolder.getAdapterPosition(), so I test for that as well):
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
oldPosition = viewHolder.getAdapterPosition();
if ( oldPosition > POSITION_UNKNOWN) {
if ( newPosition != target.getAdapterPosition() ) {
newPosition = target.getAdapterPosition();
return true;
}
}
return false;
}
Only when onMove() returns TRUE is onMoved() in the Callback subsequently called. It is in that method that I call my implementation of onViewMoved():
#Override
public void onMoved(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder, int fromPos, #NonNull RecyclerView.ViewHolder target, int toPos, int x, int y) {
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y);
implementor.onViewMoved(fromPos, toPos);
}
That is how I solved the first issue of needing to consolidate all the onMove() callbacks into a single update. I only get a call a single time now as the user drags over individual viewholders.
As to the second issue - like you I have disabled the data listening in the RealmRecyclerView. I used the technique in the article to utilize an image in my viewholder to instigate the drag (in my case, it is a thumbnail image representing the individual entry in the recyclerview). This is done as the last step in the onBindViewHolder() method:
holder.getMediaImageView().setOnTouchListener((View view, MotionEvent motionEvent) -> {
if (motionEvent.getAction()== MotionEvent.ACTION_DOWN) {
// Turn off the data change listener within the RealmRecyclerViewAdapter superclass, so that
// when we make changes via the drag it doesn't automatically update - we will handle that manually
onDetachedFromRecyclerView(mMediaRecyclerView);
mItemTouchHelper.startDrag(holder);
}
return false;
});
Now when my onViewMoved() method is called, I can adjust the data model manually and notify the adapter:
public void onViewMoved(int oldPosition, int newPosition) {
Timber.d("Recognized a movement to a new position: old position, new position is: %d, %d", oldPosition, newPosition);
MediaDataMgr.get().moveMediaInPlaylist(oldPosition, newPosition, mPlaylist.getId());
// RealmRecyclerViewAdapter is no longer automatically notified when underlying data changes, so need to make notify()* calls here
notifyItemMoved(oldPosition, newPosition);
}
MediaDataManager is a class I wrote that consolidates all my Realm access. Here is that method as an example of how I do this (as well as the technique of insuring I'm calling Realm from the same thread I am currently on):
public void moveMediaInPlaylist(int from, int to, String playlistId) {
boolean success = false;
boolean mainThread = Thread.currentThread().equals(Looper.getMainLooper().getThread());
Realm realm = null;
if (mainThread) {
realm = mUIThreadRealm;
} else {
realm = Realm.getDefaultInstance();
}
try {
Playlist p = getPlaylist(playlistId);
if ( p== null) {
throw new Exception("Error retrieving playlist with ID: "+ playlistId );
}
realm.beginTransaction();
p.getMediaList().move(from, to);
success = true;
} catch (Exception e) {
Timber.d( "Exception deleting a Media in Playlist: %s", e.getMessage());
success = false;
} finally {
if ( success ) {
realm.commitTransaction();
} else {
realm.cancelTransaction();
}
if (!mainThread) {
realm.close();
}
}
}
Then when clearView() is called in the Callback, I forward this to the adapter via the interface definition. In Callback:
public void clearView(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
oldPosition = POSITION_UNKNOWN;
newPosition = POSITION_UNKNOWN;
implementor.onClearView(recyclerView, viewHolder);
}
And in adapter implementation of the interface I use this to re-attach the data listener:
public void onClearView(#NonNull RecyclerView recyclerView, #NonNull RecyclerView.ViewHolder viewHolder) {
// reset RealmRecyclerViewAdapter data listener
onAttachedToRecyclerView(mMediaRecyclerView);
}
Now I am able to get a smooth looking animation, updating the DB in real time only once as items are dragged down the recyclerview using Realm:
I have a weird problem.
Achieved
I have 4 Fragments in a ViewPager attached with TabLayout. The first one is QR-code Scanner, which shows a camera. I wanted to start camera only when the Fragment is visible. For this, I override the Fragment's method setUserVisibleHint.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser) {
checkPermissionForCamera(); //it checks permission and start camera
} else {
stopCamera();
}
}
And it's working absolutely fine.
Desired
Now, what I want to achieve is that when Fragment is not visible (or is being visible by scrolling), it shows a view with background over camera, so that instead of camera, that cover is visible. Like image below.
For it, I edited startCamera and stopCamera, now it looks like below,
public void startCamera() {
if(cameraCover != null)
cameraCover.setVisibility(View.GONE);
isCameraStarted = true;
mScannerView.setResultHandler(this); // Register ourselves as a handler for scan results.
mScannerView.startCamera();
}
public void stopCamera() {
if(cameraCover != null)
cameraCover.setVisibility(View.VISIBLE);
if(mScannerView != null) {
isCameraStarted = false;
mScannerView.stopCamera();// Stop camera on pause
mScannerView.stopCameraPreview();// Stop camera preview
}
}
But the gray cover is only visible for the first time, rest of the time I get the camera opened with view on which it's scrolled, like below.
I've also tried to override onPageScrolled of OnPageChangeListener, but with no luck. Here's what I've done.
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if(position == 0) {
if(positionOffset > 0.7) {
fragment = adapter.getItemAtPosition(0); //fragment is at zero
if(fragment != null) {
if(fragment instanceof ScanQRFragment) {
((ScanQRFragment) fragment).stopCamera();
}
}
}
}
}
#Override
public void onPageSelected(int position) {
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Try either of the following ways :
1- override setMenuVisibility :
#Override
public void setMenuVisibility(final boolean visible) {
if (visible) {
//start camera preview
}
super.setMenuVisibility(visible);
}
2- Check yourFragment.isResumed() instead of isVisible()
I'm using 3 Fragments inside a Viewpager, the problem is that I am loading big data in an Asynctask and loaders. On devices like HTC one, it works fine, however, on low-end devices, it takes a lot of time. This is mainly because when I implement the pagerAdapter, I put the Fragments inside an ArrayList, this force the fragments instantiate when the main activity is loaded. What I need is that it just "load" the first fragment (main) and when the user Swype, load the other fragment. its any way to achieve this? this is my pageAdapater
public class PagerAdapter extends FragmentPagerAdapter {
private final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
// private final ArrayList<String> titulos = new ArrayList<String>();
// private int NUM_PAGES =0;
public PagerAdapter(FragmentManager manager,int num_pages) {
super(manager);
// this.NUM_PAGES = num_pages;
}
public void addFragment(Fragment fragment,String title) {
mFragments.add(fragment);
notifyDataSetChanged();
}
#Override
public int getCount() {
//return NUM_PAGES;
return mFragments.size();
}
#Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
}
The above method from Sun did not work for me (maybe it does for you), but I thought I would share my edit of his method also. Very good method by the way Sun!
private boolean _hasLoadedOnce= false; // your boolean field
#Override
public void setUserVisibleHint(boolean isFragmentVisible_) {
super.setUserVisibleHint(true);
if (this.isVisible()) {
// we check that the fragment is becoming visible
if (isFragmentVisible_ && !_hasLoadedOnce) {
new NetCheck().execute();
_hasLoadedOnce = true;
}
}
}
I'm gonna add my solution here since I faced a similar issue. My asynchronous task wasn't loading huge amounts of data, but it prevents unnecessary network calls. Here's what I added in my Fragment:
private boolean _hasLoadedOnce= false; // your boolean field
#Override
public void setUserVisibleHint(boolean isFragmentVisible_) {
super.setUserVisibleHint(isVisibleToUser);
if (this.isVisible()) {
// we check that the fragment is becoming visible
if (!isFragmentVisible_ && !_hasLoadedOnce) {
//run your async task here since the user has just focused on your fragment
_hasLoadedOnce = true;
}
}
}
With the above code, your Fragment will be loaded, but your async task will not run until the user actually scrolls to the Fragment for the first time. Once displayed, your async task will run for the first time automatically. Then you can provide a way to load more data via a button or pull to refresh. The above Fragment was in my ViewPager and seemed to work fine.
Slightly modified version to fix potential NPE caused by some views not fully initialised
private boolean _hasLoadedOnce= false; // your boolean field
#Override
public void setUserVisibleHint(boolean isFragmentVisible_) {
super.setUserVisibleHint(isVisibleToUser);
if (this.isVisible()) {
// we check that the fragment is becoming visible
if (!isFragmentVisible_ && !_hasLoadedOnce) {
new Handler().post(() -> {
makeAsyncRequest();//do your asyn stuffs
_hasLoadedOnce = true;
});
}
}
}
Use fragmentStatePageAdapter if you have a lot of pages and you want to destroy them when not visible.
It has implemented a setMenuVisibility(boolean menuVisible) when fragment becomes visible, so use that.
I might be late for the party but here's my solution and it works as expected. In all of your child fragments create a boolean variable:
private boolean loadFragmentExecuted = false;
in the child fragments create a generic method called loadFragment and move all of the logic you added in onCreateView to that method:
public void loadFragment()
{
if(!loadFragmentExecuted)
{
//Add your logic to manipulate the UI or load data etc...
loadFragmentExecuted = true;
}
}
in your pageview logic create the fragments dynamically like:
//add the fragment
String fragmentName = "com.something." + fragmentId;
//check if the class exists
try
{
Class myFragmentClass = Class.forName(fragmentName);
Fragment myFragment = (Fragment) myFragmentClass.newInstance();
mFragments.add(myFragment);
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
catch (IllegalAccessException e)
{
e.printStackTrace();
}
catch (InstantiationException e)
{
e.printStackTrace();
}
then set your pager adapter and attach a tablayout with it:
//set our pager adapter that contains different fragments
mPagerAdapter = new BasePagerAdapter(mFragmentManager, mFragments);
//link the adapter to the viewpager
mViewPager.setAdapter(mPagerAdapter);
//cache fragments
int limit = (mPagerAdapter.getCount() > 0 ? mPagerAdapter.getCount() : 1);
mViewPager.setOffscreenPageLimit(limit);
//add the page listner to the viewPager and link it to the tabLayout
mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));
//on tab selected select current viewpager item
mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener()
{
#Override
public void onTabSelected(TabLayout.Tab tab)
{
mViewPager.setCurrentItem(tab.getPosition());
//get fragment for the selected tab
Fragment f = mPagerAdapter.getItem(tab.getPosition());
//load the content of the fragment
try
{
Class c = f.getClass();
Method loadFragment = c.getMethod("loadFragment");
loadFragment.invoke(f);
}
catch (IllegalAccessException e){}
catch (InvocationTargetException e){}
catch (NoSuchMethodException e){}
}
#Override
public void onTabUnselected(TabLayout.Tab tab)
{
}
#Override
public void onTabReselected(TabLayout.Tab tab)
{
}
});