PagerAdapter changed adapters content with notifying - java

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.

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);
});
}
}

Android Listener working in activity but not in fragment

I have an activity and two fragments. Im trying to get a clicked item from an arraylist. In my fragmentA I have a interface:
public interface GroupListener {
public String onGroupSelected(String groupName);
}
and
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
try {
groupListener = (GroupListener) context;
} catch (ClassCastException e)
{
throw new ClassCastException(context.toString() + " must implement the interface" +
"called GroupListener!");
}
}
This gets the postion from an item clicked in a list :
public void onListItemClick(#NonNull ListView l, #NonNull View v, int position, long id) {
groupListener.onGroupSelected((String) getListAdapter().getItem(position));
}
I then have the listner in my activity and in fragmentB which looks the same:
#Override
public String onGroupSelected(String groupName) {
System.out.println("in onGroup in Activity");
return groupName;
}
#Override
public String onGroupSelected(String groupName) {
System.out.println("in onGroup in fragmentB");
return groupName;
}
But when i click an item only the listener in my activity is activated, not the one in my fragment. What is it that im missing?
The one in FragmentB will not fire, since you are only communicating with the activity from fragment A.
In your activity do something like this:
Override
public String onGroupSelected(String groupName) {
System.out.println("in onGroup in Activity");
FragmentB frag = //find your fragment using fragment manager
frag.onGroupSelected(groupName);
return groupName;
}
This is how you call methods inside fragment from an activity
It is a little late to answer but I found a way that works for me.
As listener will first get by activity, you can define destination fragment in the Main activity and then call a method in the destination fragment and do what you want:
In the main Activity listener
DestinationFragment fragment = (DestinationFragment) getSupportFragmentManager().findFragmentById(R.id.destination_fragment);
fragment.sampleListener(bundle);
In the destination fragment define sampleListener and write your code.

Opening multiple activities from recyclerview causes the app to lag

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.

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.

Grasping the fragment of a viewpager

I have a view pager with 2 fragments. One of them has a blue screen that should be hidden once the fragment is selected. So, I want to grasp a pointer to this fragment that I can call a certain method on once the user selects this fragment. I have implemented the addOnPageChangeListener interface. I want to call that method, to hide the blue screen, in the onPageSelected call.
My question is how can I grasp a pointer to the current selected fragment?
I have tried the following:
AnswersFragment fragment = (AnswersFragment) getSupportFragmentManager().findFragmentByTag("android:switcher:" + this.pager + ":" + position);
if (fragment == null)
fragment = new AnswersFragment();
But it always returns null.
I have also tried to keep an instance variable called currentAnswerFragment, but it doesn't necessarily point to the current fragment as the view pager keeps track of 3 fragments at the same time.
Also, my model is not an array (or 2) of fragments. I call a function that initializes a fragment and return it. With that said, I can't use the index of the current item to get the fragment being displayed.
You need to override onPageSelected method and fire respected method on desired screen, for example:
#Override
public void onPageSelected(int position) {
if (position == 0) {
callForPage();
} else if (position == 1) {
callForPage1();
}
}
and you can use following code to go to selected screen.
viewPager.setCurrentItem(pageposition,false);
setCurrentItem takes two parameters: item number and boolean for smooth scrolling.
UPDATE
make a PageAdapter class for example
public class PagerAdapter extends FragmentPagerAdapter {
Context mcontext;
public PagerAdapter(FragmentManager fm, Context context) {
super(fm);
mcontext = context;
}
#Override
public Fragment getItem(int position) {
if (position == 0) {
return new FragmentA();
}
else if(position==1){
return new FragmentB();
}
else
return new FragmentC();
}
#Override
public int getCount() {
return 3;
}
}
Then in your Activity class
mViewPager.setAdapter(new PagerAdapter(getSupportFragmentManager(), this));
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
///Here you handle the pointer thing
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Edit:
A better implementation of the solution I came up with is found here
After a while of debugging, I figured out a solution.
By keeping an external data structure of the previous fragments visited. In my case I kept a HashMap as a normal array won't work. In some cases I call the pager.setCurrentItem(position). Meaning that I jump fragments. So a HashMap is more appropriate.

Categories

Resources