Tobi Oyelekan
Tobi Oyelekan

Reputation: 372

Fragment loses focus in ViewPager

I am working on a music player. There are two activities. The first activity is a ViewPager which contains four fragments (All songs, Playlist, Albums and Artist). The other Activity is used to list the songs of a particular playlist. Everything works fine when playing music from the first fragment (i.e. all songs) in the ViewPager. Now when I open a playlist from the second fragment it opens up the new Activity listing the songs in that playlist. When I click to play, it plays fine. However, when I go back to the previous activity (i.e. ViewPager) and I try to play a song in the 1st fragment (i.e. all songs), it plays the song from the predecessor playlist which is from the previous Activity. It seems like the Fragment loses its focus despite it been visible and its RecyclerView click doesn't work. It works for the previous, maybe because it uses the same adapter class for their RecyclerView. I don't know what am I doing wrong here. Please help.

This is my ViewPager code.

public class Home extends AppCompatActivity {
    ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());

        // Set up the ViewPager with the sections adapter.
        mViewPager = (ViewPager) findViewById(R.id.container);
        mViewPager.setAdapter(mSectionsPagerAdapter);

        TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.setupWithViewPager(mViewPager);

        Intent service = new Intent(this, MyMusicPlayerService.class);
        bindService(service, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    public class SectionsPagerAdapter extends FragmentPagerAdapter {

        public SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            Fragment fragment = null;
            switch (position) {
                case 0:
                    fragment = new MyMusic();
                    break;
                case 1:
                    fragment = new PlayList();
                    break;
                case 2:
                    fragment = new Albums();
                    break;
                case 3:
                    fragment = new Artist();
                    break;
            }
            return fragment;
        }

        @Override
        public int getCount() {
            return 4;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            switch (position) {
                case 0:
                    return getResources().getString(R.string.title1);
                case 1:
                    return getResources().getString(R.string.title2);
                case 2:
                    return getResources().getString(R.string.title3);
                case 3:
                    return getResources().getString(R.string.title4);
            }
            return null;
        }
    }
}

The All Songs Fragment: I am writing the onCreateView() method first fragment of the ViewPager.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_my_music, container, false);
    storage = new Storage(getActivity());
    rvAdapter = new RVAdapter(musics, getActivity(), "all");
    layoutManager = new LinearLayoutManager(getActivity());
    recycler = (RecyclerView) view.findViewById(R.id.recycler);
    recycler.setLayoutManager(layoutManager);

    rvAdapter.setOnItemClickListener(new RVAdapter.OnRvItemClickListener() {
        @Override
        public void onRvItemClickListener(ArrayList<String> data, int position) {
            storage.storeSongs(musics); //stores all musics of the current list in an Array, in case of next and prev
            mListener.onFragmentInteraction(position);
        }
    });

    rvAdapter.setOnOptionClickListener(new RVAdapter.OnOptionClickListener() {
        @Override
        public void onOptionClick(String mode, ArrayList<String> data) {
            processOption(mode, data);
        }
    });

    populate(); // function to populate recyclerview
    recycler.setAdapter(rvAdapter);
    return view;
}

Second Activity: This is the onCreate() method for the second activity that lists the song of a particular playlist opened from the 2nd tab(Playlist) in the first activity.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_list_song);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    LocalBroadcastManager.getInstance(this).registerReceiver(updateReceiver, new IntentFilter(Constants.ACTION.PLAY_INFO));

    storage = new Storage(this);
    playinfo = (CardView) findViewById(R.id.playinfo);
    prev = (ImageView) findViewById(R.id.prev);
    playpause = (ImageView) findViewById(R.id.playpause);
    fwd = (ImageView) findViewById(R.id.fwd);

    title = (TextView) findViewById(R.id.title);
    artist = (TextView) findViewById(R.id.artist);

    playinfo.setOnClickListener(this);
    prev.setOnClickListener(this);
    playpause.setOnClickListener(this);
    fwd.setOnClickListener(this);

    layoutManager = new LinearLayoutManager(this);
    recycler = (RecyclerView) findViewById(R.id.recycler);
    recycler.setItemAnimator(new DefaultItemAnimator());
    recycler.setLayoutManager(layoutManager);
    empty = (TextView) findViewById(R.id.empty);

    rvAdapter.setOnItemClickListener(new RVAdapter.OnRvItemClickListener() {
        @Override
        public void onRvItemClickListener(ArrayList<String> data, int position) {
            storage.storeSongs(musics); //stores all musics of the current list in an Array, in case of next and prev
            Intent intent = new Intent(Constants.ACTION.PLAY_INFO);
            intent.putExtra("title", data.get(0));
            intent.putExtra("artist", data.get(1));
            intent.putExtra("playStatus", "play");
          LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
            storage.storeAudioIndex(position);
            Intent intent2 = new Intent(getBaseContext(), MyMusicPlayerService.class);
            intent2.setAction(Constants.ACTION.STARTFOREGROUND_ACTION);
            startService(intent2);//play music

            updateBottom();
        }
    });

    rvAdapter.setOnOptionClickListener(new RVAdapter.OnOptionClickListener() {
        @Override
        public void onOptionClick(String mode, ArrayList<String> data) {
            processOption(mode, data);
        }
    });

    recycler.setAdapter(rvAdapter);
}

Upvotes: 1

Views: 1591

Answers (1)

Reaz Murshed
Reaz Murshed

Reputation: 24211

This is too board to answer. However, I thought I could give you an insight of the solution to your problem and hence I am writing an answer.

From my understanding the Home activity hosts the four Fragment in a ViewPager. All of these four fragments contain RecyclerView to list different things. But hence clicking on any item in the RecyclerView in any of these four Fragment brings up the SecondActivity which contains a list of songs. So as far as I have understood, you can play songs from two different places. One is the first Fragment in the ViewPager which is labeled as All Songs and the another one is from the list that you have inside SecondActivity for all other three Fragment in the ViewPager.

Now, I would like to suggest a design flow which will help you to have control over the song that you are playing.

You need to host two Fragment in your Home activity. A Fragment will be ViewPagerContainerFragment and the other will be the SecondFragment. Convert your SecondActivity as the SecondFragment here and move the ViewPager related code from your Home activity to ViewPagerContainerFragment. By default, set the ViewPagerContainerFragment in the fragment container of the Home activity.

Let us have a public function in the Home activity that hosts these two Fragment. I am just giving you a sample, please modify the function parameter as per your requirement.

public void playSong (List<Music> musics, int position, ArrayList<String> data) {
    storage.storeSongs(musics); //stores all musics of the current list in an Array, in case of next and prev
    Intent intent = new Intent(Constants.ACTION.PLAY_INFO);
    intent.putExtra("title", data.get(0));
    intent.putExtra("artist", data.get(1));
    intent.putExtra("playStatus", "play");
    LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
    storage.storeAudioIndex(position);
    Intent intent2 = new Intent(getBaseContext(), MyMusicPlayerService.class);
    intent2.setAction(Constants.ACTION.STARTFOREGROUND_ACTION);
    startService(intent2); //play music

    updateBottom();
}

I do not know what you are exactly doing with storage and the data, but the idea is to pass necessary parameters to the playSong function to play the specific song and handle the next and previous song properly from the specific list. It would be great if you could just pass the name of the list and the index of the song from that list in the playSong function. So that we could play the song from a single place.

Now, as you have wrote the function playSong perfectly in the Home activity which handles playing a song and maintains the previous and next song along with the specific list from where the song is playing, our problem is nearly solved. Try to keep a static variable of your Service intent, so that each time you do not re-initialize the MyMusicPlayerService class.

The next step will be removing the code for playing a song from everywhere other than the playSong function that we have now in the Home activity.

From the AllSongsFragment modify the click action on RecyclerView like this.

rvAdapter.setOnItemClickListener(new RVAdapter.OnRvItemClickListener() {
    @Override
    public void onRvItemClickListener(ArrayList<String> data, int position) {
        ((Home) getActivity()).playSong(...); // Call the public function that you have declared in Home activity. 
        mListener.onFragmentInteraction(position);
    }
});

And from the SecondFragment call the playSong() function in the same manner which will play the song for you.

Another approach

I can think of another approach without changing the SecondActivity to Fragment and keeping the current structure. This might be solved using a LocalBroadcastReceiver. You might consider having a BroadcastReceiver in your Host activity and on receiving the broadcast send from the SecondActivity and AllSongsFragment it will play the song accordingly from a single place. If you want to have a basic idea on how BroadcastReceiver works, you may look into my answer here for better understanding. The idea is to play and track the song from a single static place.

Upvotes: 1

Related Questions