Opening multiple activities from recyclerview causes the app to lag - java

My app runs fine without any lag. However I have a recyclerview that opens a new activity containing item's details. So let's say activity A has recyclerview and it opens activity B. If the user opens activity B and performs onBackpressed and repeats this process the whole app starts to lag. Basically the flow of interaction that causes the app to lag is : A ->B ->A -> B ... (if user does this more than 10 times the app becomes almost unusable). I have tried leak canary but it doesn't report any memory leak. Also checking the android profile the only memory that goes up is native memory.
In adapter i have nothing at onBindViewHolder beside the bind method. The on click method is implemented through an interface.
public class ElementsAdapter extends RecyclerView.Adapter<ElementsAdapter.ElementsViewHolder> implements Filterable {
private final LayoutInflater inflater;
private ElementsRecyclerItemClickListener recyclerItemClickListener;
private List<Elements> elements;
private List<Elements> filteredElements;
public boolean isSearchRequestFiler = false;
ElementsAdapter(Context context) {
this.inflater = LayoutInflater.from(context);
}
#Override
public ElementsAdapter.ElementsViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
return new ElementsAdapter.ElementsViewHolder(RowElementsBinding.inflate(inflater, parent, false));
}
#Override
public void onBindViewHolder(final ElementsAdapter.ElementsViewHolder holder, final int position) {
holder.bind(filteredElements.get(position));
}
#Override
public void onViewRecycled(ElementsAdapter.ElementsViewHolder viewHolder) {
super.onViewRecycled(viewHolder);
}
#Override
public int getItemCount() {
return filteredElements == null ? 0 : filteredElements.size();
}
Elements getItemAt(int position) {
return filteredElements.get(position);
}
void setRecyclerItemClickListener(ElementsRecyclerItemClickListener elementClickListener) {
this.recyclerItemClickListener = recyclerItemClickListener;
}
void setElements(final List<Elements> elements) {
this.elements = elements;
this.filteredElements = elements;
notifyDataSetChanged();
}
class ElementsViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final RowElementsBinding binding;
ElementsViewHolder(RowElementsBinding binding) {
super(binding.getRoot());
this.binding = binding;
this.binding.getRoot().setOnClickListener(this);
}
public void bind(Element element) {
binding.setElement(element);
binding.executePendingBindings();
}
#Override
public void onClick(View view) {
if (recyclerItemClickListener != null)
recyclerItemClickListener.onElementClickListener(getAdapterPosition());
}
}
The on Click listener at activity A:
#Override
public void onElementClickListener(int position) {
Intent intent = new Intent(ActivityA.this, ActivityB.class);
intent.putExtra("ELEMENTS_ID", adapter.getItemAt(position).getId());
intent.putExtra("ELEMENTS_OPENED_FROM", "ACTIVITY_A");
startActivity(intent);
}
Any help would be appreciated

After trying many things and doing lots of test I figured out that the reason the lag is happening is facebook sdk (To resolve this I downgraded from 5.0.0. to 4.41.0) . So the facebook sends codeless events and they are allocated in memory and not released until, in my case, Activity A is finished. Hope this helps somebody.

Related

How can you change ViewPager2 position inside the ViewPager2Adapter?

I programmed a Vocabulary Trainer with Vocabulary Cards. The Vocabulary Cards are Entries in a Room Database created from an asset. I am displaying these Vocabulary Cards with ViewPager2 in an Activity. I have a 'correct' and a 'false' button and when the user clicks on either, I want to update the Vocabulary Card (-> The entry in the sqlite database) and automatically swipe to the next item of the ViewPager2.
If I implement the buttons in the ViewPager2Adapter, I can't find a way to change the position of the ViewPager2. If I implement the buttons in the activity the sqlite entry does not update properly (After it updates the entry, the activity is constantly refreshed, it seems like it never the leaves the OnClick methode of the button).
So is it possible to change the position of ViewPager2 from inside the ViewPager2Adpater?
Thanks for your help!
That is the relevant code if I have the buttons in my ViewPager2Adapter. Here I don't know how to change the position of the ViewPager2
public void onBindViewHolder(#NonNull #NotNull ViewHolder holder, int position) {
VocabularyCard vocabularyCard = currentCards.get(position);
holder.btn_correct.setOnClickListener(view -> {
vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard);
});
holder.btn_false.setOnClickListener(v15 -> {
vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard);
});
That is the relevant code if I have the buttons in the Activity. Here the update function triggers an infinite updating of the Activity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initAll();
btn_correct_2.setOnClickListener(view -> {
int currentPos = viewpager2.getCurrentItem();
vocabularyViewModel.getCurrentCards().observe(this, vocabularyCards -> {
if (vocabularyCards.size() == currentPos){
Intent intent = new Intent(TestActivity.this, MainActivity.class);
startActivity(intent);
}else {
viewpager2.setCurrentItem(currentPos + 1);
}
VocabularyCard vocabularyCard = vocabularyCards.get(currentPos);
vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard);
});
});
btn_false_2.setOnClickListener(view -> {
int currentPos = viewpager2.getCurrentItem();
vocabularyViewModel.getCurrentCards().observe(this, vocabularyCards -> {
if (vocabularyCards.size() == currentPos){
Intent intent = new Intent(TestActivity.this, MainActivity.class);
startActivity(intent);
}else {
viewpager2.setCurrentItem(currentPos + 1);
}
VocabularyCard vocabularyCard = vocabularyCards.get(currentPos);
vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard);
});
});
Objects.requireNonNull(getSupportActionBar()).setTitle(getResources().getString(R.string.learn_new_words));
LiveData<List<VocabularyCard>> allNewCards = vocabularyViewModel.getAllNewCards(goal);
allNewCards.observe(this, vocabularyCards -> vocabularyViewModel.setCurrentCards(vocabularyCards));
vocabularyViewModel.getCurrentCards().observe(this, vocabularyCards -> {
viewPager2Adapter.setCurrentCards(vocabularyCards);
viewpager2.setAdapter(viewPager2Adapter);
viewpager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
}
#Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
}
});
});
The update function in the Room DAO is straightforward:
#Update
void updateSingleVocabularyCard(VocabularyCard vocabularyCard);
I left out all the code that is not relevant.
There are several ways to propagate an event from the adapter to the activity where you manage your cards using ViewPager2. Let's have a look how it can be done either using an interface or using the same view model. But in any case I strongly recommend you to update your database in a background thread to prevent any possible UI lags.
1. Using an interface
This option is more flexible since you can propagate events as well as pass data as parameters. You can also reuse this interface for other cases. As far as I See you have a holder that has 2 buttons for the users to make choices. So our event here would be something like ChoiceEventListener, let's call this interface like so. Then you'd have to add a method to handle this event from within anywhere you wanna hear this event, and let's call its handle method onChoice(). Finally we would need a variable to indicate what the choice is. Now that ready to implement, let's write the new interface...
ChoiceEventListener.java
public interface ChoiceEventListener {
void onChoice(VocabularyCard vocabularyCard, boolean choice);
}
The next thing to do is to implement this interface where you want to listen to this event. In this case it is in your activity. There are 2 ways to do this:
You make your activity to inherit its methods using the implements keyword
YourActivity.java
public class YourActivity extends AppCompatActivity implements ChoiceEventListener {
// Use a background thread for database operations
private Executor executor = Executors.newSingleThreadExecutor();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initAll();
// You must construct your adapter class with the listener
ViewPager2Adapter adapter = new ViewPager2Adapter(/* Other params... */, this);
}
#Override
public void onChoice(VocabularyCard vocabularyCard, boolean choice) {
if(choice) {
// User pressed the correct button
}
else {
// User pressed the false button
}
// Update card in the background
executor.execute(()-> vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard));
}
}
You can implement it as an anonymous function
YourActivity.java
public class YourActivity extends AppCompatActivity {
// Use a background thread for database operations
private Executor executor = Executors.newSingleThreadExecutor();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initAll();
// You must construct your adapter class with the listener
ViewPager2Adapter adapter = new ViewPager2Adapter(/* Other params... */, (vocabularyCard, choice) -> {
if(choice) {
// User pressed the correct button
}
else {
// User pressed the false button
}
// Update card in the background
executor.execute(()-> vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard));
});
}
}
Finally the ViewPager2Adapter class implementation would be something like this:
ViewPager2Adapter.java
public class ViewPager2Adapter extends RecyclerView.Adapter<ViewPager2ViewHolder> {
// Here is your listener to deliver the choice event to it
private final ChoiceEventListener listener;
// Constructor
public ViewPager2Adapter(/* Other params... */, ChoiceEventListener listener) {
/* Other inits */
this.listener = listener;
}
public void onBindViewHolder(#NonNull #NotNull ViewHolder holder, int position) {
VocabularyCard vocabularyCard = currentCards.get(position);
holder.btn_correct.setOnClickListener(view -> {
listener.onChoice(vocabularyCard, true); // true for correct
});
holder.btn_false.setOnClickListener(v15 -> {
listener.onChoice(vocabularyCard, false); // false for false :)
});
}
}
2. Use the ViewModel for inter-communication
In this option we use a LiveData object to make page switching. The only thing you need to know in your activity is the current position which you get it from the adapter class. Once you update it in the adapter, set the current position value in live data so that you can switch the page in your activity.
VocabularyViewModel.java
public class VocabularyViewModel extends ViewModel {
public MutableLiveData<Integer> mldCurrentPosition = new MutableLiveData<>(0);
}
YourActivity.java
public class YourActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initAll();
vocabularyViewModel.mldCurrentPosition().observe(this, currentPosition -> {
if(currenPosition == null) return; // ignore when null
viewpager2.setCurrentItem(currentPosition + 1);
}
}
}
Finally the ViewPager2Adapter class implementation would be something like this:
ViewPager2Adapter.java
public class ViewPager2Adapter extends RecyclerView.Adapter<ViewPager2ViewHolder> {
// Use a background thread for database operations
private Executor executor = Executors.newSingleThreadExecutor();
public void onBindViewHolder(#NonNull #NotNull ViewHolder holder, int position) {
VocabularyCard vocabularyCard = currentCards.get(position);
holder.btn_correct.setOnClickListener(view -> {
// Update card in the background
executor.execute(()-> vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard));
// Then invoke switching to the next card
vocabularyViewModel.mldCurrentPosition.setValue(position + 1);
});
holder.btn_false.setOnClickListener(v15 -> {
// Update card in the background
executor.execute(()-> vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard));
// Then invoke switching to the next card
vocabularyViewModel.mldCurrentPosition.setValue(position + 1);
});
}
}

How to detect each RecyclerView item after it is displayed

I want to detect each item in my RecylerView after it is displayed to the user.
Basically, I am trying to play a sound on each item after it is loaded on the screen.
But I am not able to detect whenever each item is loaded on the screen! Is there any method I have to call to detect each item rendered
E.g 1st RecyclerView item displayed -> play sound
2st RecyclerView item displayed -> play sound...
My Adapter class looks like this -
public class AdapterListAnimation extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<Multiples> items = new ArrayList<>();
private Context ctx;
private OnItemClickListener mOnItemClickListener;
private int animation_type = 0;
.........
.........
I am calling this initComponent() method from onCreated() method. Can you give advice on what should I do to achieve my goal as described above?
private void initComponent() {
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
items = DataGenerator.getPeopleData(this,of,value);
setAdapter();
/* MediaPlayer mp=MediaPlayer.create(this, R.raw.sword);
if (mp.isPlaying()) {
mp.stop();
mp.release();
mp = MediaPlayer.create(this, R.raw.sword);
} mp.start();*/
}
private void setAdapter() {
// Set data and list adapter
mAdapter = new AdapterListAnimation(this, items, animation_type);
recyclerView.setAdapter(mAdapter);
// on item list clicked
mAdapter.setOnItemClickListener(new AdapterListAnimation.OnItemClickListener() {
#Override
public void onItemClick(View view, com.math.multiplication.model.Multiples obj, int position) {
Snackbar.make(parent_view, "Item " + obj.first + " clicked", Snackbar.LENGTH_SHORT).show();
}
});
}
you need to override onViewAttachedToWindow and onViewDetachedFromWindow. but for detecting holer type you need getItemViewType() just like that:
public class PostAdapter extends RecyclerView.Adapter {
#Override
public int getItemViewType(int position) {
switch (types.get(position)){
case 1:
return 1;
case 2:
return 2;
default:
return position;
}
}
#Override
public void onViewAttachedToWindow(#NonNull RecyclerView.ViewHolder holder) {
super.onViewAttachedToWindow(holder);
if (holder.getItemViewType() == 1){
//play song
}
}
#Override
public void onViewDetachedFromWindow(#NonNull RecyclerView.ViewHolder holder) {
super.onViewDetachedFromWindow(holder);
if (holder.getItemViewType() == 1){
//pause song
}
}
You need to add listener for TTS. Then update your RecyclerView to show right image when speech starts and ends.
I've created a test project to show how it can be implemented. Here you can see how it works. Here is my github repository.
Here is main part of my MainActivity class.
private void initTts() {
tts = new TextToSpeech(this, this);
tts.setLanguage(Locale.US);
tts.setOnUtteranceProgressListener(new MyListener());
}
#Override
public void onInit(int status) {
playSound(0);
}
private void playSound(int index) {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, String.valueOf(index));
tts.speak(data.get(index), TextToSpeech.QUEUE_ADD, hashMap);
}
class MyListener extends UtteranceProgressListener {
#Override
public void onStart(String utteranceId) {
int currentIndex = Integer.parseInt(utteranceId);
mMainAdapter.setCurrentPosition(currentIndex);
handler.post(new Runnable() {
#Override
public void run() {
mMainAdapter.notifyDataSetChanged();
}
});
}
#Override
public void onDone(String utteranceId) {
int currentIndex = Integer.parseInt(utteranceId);
mMainAdapter.setCurrentPosition(-1);
handler.post(new Runnable() {
#Override
public void run() {
mMainAdapter.notifyDataSetChanged();
}
});
if (currentIndex < data.size() - 1) {
playSound(currentIndex + 1);
}
}
#Override
public void onError(String utteranceId) {
}
}
You can simply use onViewAttachedToWindow(VH) in your adapter.
See https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#onViewAttachedToWindow(VH)
Update:
As you know RecyclerView will be call OnBindViewHolder only once for each item.
RecyclerView will not call this method again if the position of the item changes in the data set unless the item itself is invalidated or the new position cannot be determined.
So you can use onViewAttachedToWindow
(onViewAttachedToWindow) Called when a view created by this adapter has been attached to a window.
This can be used as a reasonable signal that the view is about to be seen by the user. If the adapter previously freed any resources in onViewDetachedFromWindow those resources should be restored here.
You can use it like this:
public class Adapter extends RecyclerView.Adapter<MyViewHolder> {
// rest of your code
#Override
public void onViewAttachedToWindow(MyViewHolder holder) {
super.onViewAttachedToWindow(holder);
// play your sound
}
}
Hope it helps!
If I understood your problem correctly, you want to play a sound when an item in the RecyclerView is loaded.
Hence I would like to think of a workaround here. You might consider having an item added after the sound for the previous item has been played.
I would like to provide a pseudo code for what I am trying to explain. You might consider using the MediaPlayer.OnCompletionListener for listening if your sound has stopped playing and then add the next item to the RecyclerView and then call notifyDataSetChanged on your adapter to load the next item in your RecyclerView.
Hence you might want the following modification in your adapter first.
// Declare a function in your adapter which adds new item in the RecyclerView
public void addItem(Multiples item) {
items.add(item);
notifyItemInserted(items.size());
}
Now in your Activity where you set up the RecyclerView, you need to have the following arrays.
ArrayList<Multiples> allItems = new ArrayList<Multiples>(); // This list will contain all the data
int soundToBePlayed = -1; // Initialize the value with -1
ArrayList<Integer> soundList = getAllSoundsInAList();
Modify your functions as follows.
private void initComponent() {
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setHasFixedSize(true);
allItems = DataGenerator.getPeopleData(this,of,value);
setAdapter();
}
private void setAdapter() {
mAdapter = new AdapterListAnimation(this, animation_type); // Do not pass the items list
recyclerView.setAdapter(mAdapter);
// .. Your item click listener goes here
}
As the items was removed from the constructor, you need to modify the constructor of your adapter as well. Just remove the variable which takes the items. Because we will be adding elements to the items later.
Then you need to write another function which needs to be called in your onCreate function of your activity after your call your initComponent function. The function should look like the following.
private void addItemInRecyclerViewAndPlaySound() {
soundToBePlayed++;
adapter.addItem(allItems.get(soundToBePlayed));
recyclerView.scrollToPosition(adapter.getItemCount() - 1);
Integer soundId = soundList.get(soundToBePlayed);
MediaPlayer mp = MediaPlayer.create(this, soundId);
if (mp.isPlaying()) {
mp.stop();
mp.release();
mp = MediaPlayer.create(this, soundId);
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer mp) {
// Call this function again to add next item
// and play the next sound at the same time
if(soundToBePlayed < allItems.size() - 1)
addItemInRecyclerViewAndPlaySound();
}
});
}
mp.start();
}
I have not tested the code, however, I think you get the idea already. This can be implemented gracefully using a interface inside the adapter as well. The imeplemntation technique is your choice.

How can I make a long click listener for an AppWidgetHostView

I'm making a launcher and I am stuck on making a long click listener for the widgets. I made a class that extends AppWidgetHost and another that extends AppWidgetHostView. They intercept the touch event and if it's action up it looks and sees if the action down lasted for 400L. It works ok unless there is no button on the widget. For example, the clock widget can not be long pressed.
Here is the implementation of the longClickListener on the host view:
hostView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
new AlertDialog.Builder(WidgetEdge.this)
.setTitle("Options")
.setMessage("Do you want to delete or resize widget?")
.setIcon(android.R.drawable.sym_def_app_icon)
.setNegativeButton("Delete", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int whichButton) {
removeWidget(hostView);
Toast.makeText(WidgetEdge.this, "Widget Deleted", Toast.LENGTH_SHORT).show();
}
})
.setPositiveButton("Resize", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
resizeView(hostView);
}
}).show();
return false;
}
});
Here is the AppWidgetHostView class:
public class LauncherAppWidgetHostView extends AppWidgetHostView{
private LayoutInflater mInflater;
WidgetEdge context;
private OnLongClickListener longClick;
private long down;
public LauncherAppWidgetHostView(Context context) {
super(context);
this.context = (WidgetEdge) context;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public void setOnLongClickListener(OnLongClickListener l) {
this.longClick = l;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean trueOrFalse = false;
switch(ev.getAction()) {
case MotionEvent.ACTION_DOWN:
down = System.currentTimeMillis();
this.getParent().requestDisallowInterceptTouchEvent(true);
trueOrFalse = false;
break;
case MotionEvent.ACTION_UP:
boolean upVal = System.currentTimeMillis() - down > 400L;
if( upVal ) {
longClick.onLongClick(LauncherAppWidgetHostView.this);
trueOrFalse = true;
}
break;
}
return trueOrFalse;
}
#Override
protected View getErrorView() {
return mInflater.inflate(R.layout.appwidget_error, this, false);
}
}
Here is the AppWidgetHost:
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
class LauncherAppWidgetHost extends AppWidgetHost {
LauncherAppWidgetHost(Context context, int hostId) {
super(context, hostId);
}
#Override
protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
return new LauncherAppWidgetHostView(context);
}
#Override
public void stopListening() {
super.stopListening();
clearViews();
}
}
I have tried using the code from this link but when I tested on the clock widget it launches the onLongClickListener twice. Also when the widget is scrolled, without a long press, it would also launch the onLongClick. Thank you for any help.
--UPDATE--
I was using the debugger and found out that when using the clock widget the only event intercepted was the first ACTION_DOWN. After that it never picked up the ACTION_UP.
If the widget doesn't behave like a button (so when it can't be clicked) you need to do something more advanced to detect the long click.
You can have a look at https://github.com/willli666/Android-Trebuchet-Launcher-Standalone/blob/master/src/com/cyanogenmod/trebuchet/LauncherAppWidgetHostView.java
If the Apache licence works for your project, you can copy-paste the whole file, you just need to remove getErrorView() and the inflater and you're good to go.
The idea is to start a timeout when detecting the initial ACTION_DOWN event, and when the timeout triggers, if the view still has focus, then you can performLongClick().
It's much harder to accomplish than one would expect, but at least this works on all widgets, even those that can't be clicked.

PagerAdapter changed adapters content with notifying

So first things first, here's the error that I'm getting:
java.lang.IllegalStateException: The application's PagerAdapter
changed the adapter's contents without calling
PagerAdapter#notifyDataSetChanged! Expected adapter item count: 18,
found: 28
I'm using a RecyclerView in my main activity and that has a List<Objects> as the dataset, that's all fine.
From that activity I call the second activity when a RecyclerView item is clicked that is basically a gallery implemented using a ViewPager using this code:
public void startSlideActivity(final int position) {
DataTransferer.get().storeItems(feed);
Intent i = new Intent(context, SlideActivity.class);
...
i.putExtra("POSITION", position);
context.startActivity(i);
}
My data is too large to transfer through an intent (using Parcelable or otherwise) so I'm using a singleton to hold and transfer my list, here's the code:
public class DataTransferer {
private static volatile DataTransferer singleton;
private List<Thing> items;
public static DataTransferer get(){
if (singleton == null) {
synchronized (DataTransferer.class) {
singleton = new DataTransferer();
}
}
return singleton;
}
private DataTransferer(){
}
public void storeItems(List<Thing> items){
this.items = items;
}
public List<Thing> getStoredItems(){
return items;
}
}
In the second activity I set the adapter and retrieve the list like so:
feed = DataTransferer.get().getStoredItems();
final int position = this.getIntent().getIntExtra("POSITION", 0);
adapter = new FeedPagerAdapter(SlideActivity.this, feed);
viewPager.setAdapter(adapter);
adapter.notifyDataSetChanged();
viewPager.setCurrentItem(position);
viewPager.setOffscreenPageLimit(4);
And finally here's in my PagerAdapter code:
public class FeedPagerAdapter extends PagerAdapter {
#BindView(R.id.item_image_view) ImageView image;
private final SlideActivity host;
private List<Thing> items;
public FeedPagerAdapter(SlideActivity host, List<Thing> items){
this.host = host;
this.items = items;
notifyDataSetChanged();
}
#Override
public Object instantiateItem(ViewGroup parent, int position) {
View view = LayoutInflater.from(host).inflate(R.layout.item, parent, false);
ButterKnife.bind(this, view);
...
parent.addView(view);
return view;
}
#Override
public int getCount() {
return items.size();
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
I've tried notifying the dataset in onResume and onPause and getItemCount in the adapter also, same problem.
Back to the main activity, this data is loaded over the network and adds items to the list when the load finishes. If I start my application and click on the item as soon they start to populate the RecyclerView, it opens the second activity and I get the crash. If I start the app and wait a second and click a RecyclerView item, it works as intended.
If anyone has any suggestions on how I can wait to make sure the list is stable when starting the second activity or a better way to implement a grid based RecyclerView gallery to open a viewpager type layout with the same dataset I would really appreciate it.
It's because you are using this line viewPager.setOffscreenPageLimit(4);, It's mean viewpager won't re-create the screen in all 4 pages. However, there is a function to detect if your screen has been visible completely, it's call setUserVisibleHint(). You just need to use like below:
//For the Fragment case
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if (isVisibleToUser) {
//TODO notify your recyclerview data over here
}
}
EDIT:
For the Activity case: If targeting API level 14 or above, one can use
android.app.Application.ActivityLifecycleCallbacks
public class MyApplication extends Application implements ActivityLifecycleCallbacks {
private static boolean isInterestingActivityVisible;
#Override
public void onCreate() {
super.onCreate();
// Register to be notified of activity state changes
registerActivityLifecycleCallbacks(this);
....
}
public boolean isInterestingActivityVisible() {
return isInterestingActivityVisible;
}
#Override
public void onActivityResumed(Activity activity) {
if (activity instanceof MyInterestingActivity) {
isInterestingActivityVisible = true;
}
}
#Override
public void onActivityStopped(Activity activity) {
if (activity instanceof MyInterestingActivity) {
isInterestingActivityVisible = false;
}
}
// Other state change callback stubs
....
}
Register your application class in AndroidManifest.xml:
<application
android:name="your.app.package.MyApplication"
android:icon="#drawable/icon"
android:label="#string/app_name" >
Add onPause and onResume to every Activity in the project:
#Override
protected void onResume() {
super.onResume();
MyApplication.activityResumed();
}
#Override
protected void onPause() {
super.onPause();
MyApplication.activityPaused();
}
In your finish() method, you want to use isActivityVisible() to check if the activity is visible or not. There you can also check if the user has selected an option or not. Continue when both conditions are met.

RecyclerView onItemClickListener and View.onClickListener inside adapter

i'm developing a simple File Manager. This File Manager is inside a Fragment and, obviously, it use a RecyclerView to show folders and files. The layout that manage the row of list is composed by: 1 ImageView, 1 TextView (to show folder/file name) and one ImageView to select options (such as: rename, delete, etc.).
To manage click on RecyclerView i implemented a custom OnItemClickListener interface. The code is:
public class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
GestureDetector gestureDetector;
OnItemClickListener listener;
public interface OnItemClickListener {
public void onItemClick(View view, int position);
}
public OnRecyclerItemClickListener(Context context, OnItemClickListener listener) {
this.listener = listener;
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
#Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
});
}
#Override
public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
View childView = view.findChildViewUnder(e.getX(), e.getY());
if(childView != null && listener != null && gestureDetector.onTouchEvent(e)) {
((OnItemClickListener) listener).onItemClick(childView, view.getChildAdapterPosition(childView));
}
return false;
}
#Override
public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) {
// TODO Auto-generated method stub
}
#Override
public void onRequestDisallowInterceptTouchEvent(boolean arg0) {
// TODO Auto-generated method stub
}
}
So, in the Fragment:
recyclerView.addOnItemTouchListener(new OnRecyclerItemClickListener(activity, new OnRecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
// TODO Auto-generated method stub
}
}));
All works fine but now i have a problem: to manage the options when the user click on ImageView don't work properly because the RecyclerView.OnItemClickListener win on imageView.setOnClickListener. When the user click on the ImageView a PopupMenu is showed BUT always disappear because occur simultaneously RecyclerView.OnItemClickListener AND ImageView.OnClickListener. The first win to the second. How can i solve this?
You should change your click functionality to being handled within the ViewHolder you could then choose where to place your click listener. Doing this will allow you to place a clickable view inside your layout with the clickable ImageView being over the top of that. Below is a short example of doing this. You will need to fill in the gaps, as well as setting up the Interface properly. Let me know if you need any more help.
public interface ViewHolderListener {
void itemClicked(int position);
void imageClicked(int position);
}
public void fillView(ViewHolderItem item) {
clickableView.setOnClickListener(new View.OnClickListener {
viewHolderListener.itemClicked(getAdapterPosition());
});
yourImage.setOnClickListener(new View.OnClickListener {
viewHolderListener.imageClicked(getAdapterPosition());
});
}
At First you need to understand that the whole view will be come under Recycler View (Imageview +textview ). Now If you want to have two separate events for both Recycler view and ImageView click the ideal way should be you write them off in Adapter class of the Recyler view rather than creating an interface.
In the adapter class you write the onclick listener of the recycler view itself. like:
view.setonClickListener(new View.OnClickListener(){
onClick){}
}
and for ImageView you write something like
imageview.setonClickListener(new View.OnClickListener(){
onClick){
// This code will not call --View.onClick of your Recycler View--}
}
It is the only way that both can operate separately.

Categories

Resources