Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wait with operation until Fragment's Views are created

I am programming a Music Player and the Music plays in a background Service. When the user kills the Activity which hosts 3 Fragments, and then restarts the Activity again, I send a Broadcast from the Service that contains information about the current playing song, and the list of songs that the user added to his session.

The problem is, every time I want to set the last information into the Fragments nothing happens because their creation takes too long, and the Broadcast doesn't get handled like they should.

How can I let the Service or the Broadcast wait until the Fragments are created so they are handled appropriately?

Here are the relevant code snippets:

//When the activity binds to the service, refresh the fragments
private ServiceConnection conn = new ServiceConnection() {
    public void onServiceConnected(ComponentName className, IBinder service) {
        LocalBinder binder = (LocalBinder) service;
        myService = binder.getService();
        myService.setBound(true);
        if(myService.startedOnce) {
            myService.refreshFragments();
        }
        myService.startedOnce = true;
    }
}

//This is the broadcast receiver of the Fragment
//it receives the broadcast too soon, and I can't set the
//Views so they are always empty.
public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();
    if (action.equals(MusicService.REFRESH_ALL)) {
        Song current = intent.getParcelableExtra("song");
        setCurrentSong(current);
    }
}
like image 455
tolgap Avatar asked Nov 10 '12 12:11

tolgap


People also ask

What does the onCreateView () method return if a fragment doesn't have any UI?

These files contain only the onCreateView() method to inflate the UI of the fragment and returns the root of the fragment layout. If the fragment does not have any UI, it will return null.

Why is it recommended to use only the default constructor to create a fragment?

Traditionally, a Fragment instance could only be instantiated using its default empty constructor. This is because the system would need to reinitialize it under certain circumstances like configuration changes and the app's process recreation.

What are the methods you can override in the fragment class?

The Fragment class has two callback methods, onAttach() and onDetach() , that you can override to perform work when either of these events occur.

What is retained fragment in Android?

A Fragment represents a reusable portion of your app's User Interface. Retained Fragment consists of the configuration change that causes the underlying Activity to be destroyed. The term "retained" refers to the fragment that will not be destroyed on configuration changes.


2 Answers

The easiest thing to do would simply be hold on to the information until the Fragment is ready to display it. Use the Fragment's setArguments() method to attach the information into the Fragment.

@Override
public void onReceive() {
   String action = intent.getAction();
   if(action.equals(MusicService.REFRESH_ALL)) {
      // Creating a new Bundle so the Fragment can control its own object
      Bundle args = new Bundle(intent.getExtras()); 
      Fragment fr = getUsingFragment();
      fr.setArguments(fr);
   }
}

Then, in the Fragment's onCreateView() simply pull the arguments from getArguments() and build the view with the values.

@Override
public void onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
   Bundle args = getArguments();
   if(args != null) {
      // code to set values if arguments are set
   } else {
      // code to set values if arguments are not set
   }
}

Another way to do it would be to use setter methods in which the Fragment itself puts values into a Bundle for setArguments(). That way, you can update the views whenever the View has been created on top of setting the arguments for the possible event when the Fragment's View is destroyed and must be recreated.

Note: You can only call setArguments() before the Fragment has been attached to the Activity. You can however update the Bundle that you pass in by setArguments by retrieving a reference to it from getArguments(), then simply putting in the values. So instead of calling setArguments() from your receiver, do something like this:

public void setCurrentSong(Song extra) {
   Bundle args = getArguments();
   args.putParcable(KEY_MAP, extra);
   if(/* Views are created */) {
      // update and invalidate the views
   }
}
like image 193
DeeV Avatar answered Oct 18 '22 19:10

DeeV


How I fixed this

As I was using a Service for Media Playback, I wanted to bring up last listened songs from the service so I could directly play it. This was old logic, but I actually built my code around it. Until thusfar I bumped into it.

This was happening

  • FragmentActivity is created
  • Service gets started and bound to
  • Meanwhile the Fragments get created asynchronously
  • As soon as the Service starts, it sends out a Broadcast with latest information
  • Because the both the Service and the Fragment creations are asynchronous, the broadcast would be sent from the service, but because the BroadcastReceivers in the Fragments weren't even initialized yet, they would not receive the Intent.

What I did to fix it

I somehow had to use a callback that made sure that

  • the Service was created and bound to
  • the fragments created and views are set

So I used the ServiceConnection and to be precise, the onServiceConnected() method. There I got the preferences in which the last song was saved, and then send out the Broadcast and the Fragments received it and the Views were appropiately set. This also worked for orientation changes.

The code

//This is the code in the FragmentActivity
private ServiceConnection conn = new ServiceConnection() {
    public void onServiceConnected(ComponentName className, IBinder service) {
        LocalBinder binder = (LocalBinder) service;
        myService = binder.getService();
        myService.setBound(true);
        if (myService.startedOnce) {
            myService.refreshFragments();
        } else {
            sendLastSavedSong();
        }
        myService.startedOnce = true;
    }

    public void onServiceDisconnected(ComponentName className) {
        myService.setBound(false);
    }
};
like image 31
tolgap Avatar answered Oct 18 '22 19:10

tolgap