0

Right now I'm trying to become more familiar with Android development. Of course, I am using Android Studio to do this. As a project, I am writing an application to handle streaming from a URL with the MediaPlayer class.

I got this functional and does exactly what I need it to do. However, I did not want my MainActivity to be cluttered with methods and variables that could be stored in their own classes.

I have a couple questions about this:

  • Is it conventional to do this? Or should I keep all code and methods inside of their respective Activities, or is it conventional to move them to their own class, and access them from whichever Activity needs it?

  • I am having issues getting the MediaPlayer to work correctly when moving it to another class. I read that I must give the methods Context when calling that method. So I called the method using my object with Context passed, as you will see below.

I am fairly new to this sort of development, so tips, good habits, and helpful hints are always welcome.

Here is my MainActivity that calls the methods I put in the Streaming class:

public class MainActivity extends AppCompatActivity {

    Streaming stream = new Streaming(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    stream.updateSeekBar();
    stream.onPlayClick();
    stream.onDrag();

}

You can see here, all of those methods I called:

public class Streaming extends AppCompatActivity {

private SeekBar musicSeek;
private TextView currentTime;
private TextView totalTime;
private ImageButton play_pause;
private Handler seekHandler = new Handler();
private MediaPlayer mp = new MediaPlayer();
Context context;
Utilities util = new Utilities();

//Default Constructor
public Streaming(){}
//Contructor
public Streaming(Context context){
    this.context = context;
}
//Method to run the runnable to update the seekbar
public void updateSeekBar(){
    seekHandler.postDelayed(mUpdateTimeTask, 100);
}

public void prepareStreaming() throws IOException {
    mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mp.setDataSource("http://tricountynaz.net/media/audio/2017-11-08-The%20Compassion%20of%20the%20Christ.mp3");
    mp.prepare();
}

public void startStreaming(){
    mp.start();
}

public void pauseStreaming(){
    mp.pause();
}

//Runnable to update the seekbar with the current position.
private Runnable mUpdateTimeTask = new Runnable() {
    public void run() {
        int totalDuration = mp.getDuration();
        int currentPosition = (mp.getCurrentPosition());


        //Displaying Total Duration time
        totalTime = (TextView)((Activity)context).findViewById(R.id.totalTime);
        totalTime.setText(util.milliSecondsToTimer(totalDuration));
        // Displaying time completed playing
        currentTime = (TextView)((Activity)context).findViewById(R.id.currentTime);
        currentTime.setText(util.milliSecondsToTimer(currentPosition));
        //Set the bars total duration, based on the song duration (converted to seconds)
        musicSeek = (SeekBar)((Activity)context).findViewById(R.id.music_seek);
        musicSeek.setMax(totalDuration/1000);
        // Updating progress bar
        musicSeek.setProgress(mp.getCurrentPosition()/1000);

        // Running this thread after 100 milliseconds
        seekHandler.postDelayed(this, 100);
    }
};

//What happens when the user interacts with the button
public void onPlayClick (){
    play_pause = (ImageButton)((Activity)context).findViewById(R.id.playButton);
    play_pause.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View view) {
            play_pause.setImageResource(R.drawable.ic_pause_name);

            try {
                prepareStreaming();
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (mp.isPlaying()) {
                pauseStreaming();
                play_pause.setImageResource(R.drawable.ic_play_name);


            } else {
                //mediaPlayer.start();
                startStreaming();
            }
        }
    });
}

//Handles when the user interacts with the seekbar
public void onDrag(){
    musicSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
            if(b) {
                //seeks to the current position of the slider when the user (b) interacts.
                mp.seekTo(i*1000);
                musicSeek.setProgress(i);
            }
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {

        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {

        }
    });}

You will notice a Utilities class. This, at the moment, only holds a method to convert to seconds so I can update a TextView Item of the current progress of the MP3 being streamed.

The specific errors I am receiving are NullPointerException coming from the onDrag method. I cannot seem to understand why.

Sorry if this is a duplicate, I could not seem to find this anywhere else.

EDIT I did change my onDrag method a little:

//Handles when the user interacts with the seekbar
public void onDrag(){
    musicSeek = (SeekBar)((Activity)context).findViewById(R.id.music_seek);
    musicSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
            if(b) {
                //seeks to the current position of the slider when the user (b) interacts.
                mp.seekTo(i*1000);
                musicSeek.setProgress(i);
            }
        }

This did fix the nullPointerException, except now I get this errors in the log:

E/MediaPlayerNative: Attempt to call getDuration in wrong state: mPlayer=0x0, mCurrentState=0

EDIT 2 I think I resolved my issue. Upon researching my most recent error, I had come to realize I never called my MediaPlayer in my MainActivity, therefore nothing was being loaded and nothing streamed. Here is where I found part of my solution.

Still running into issues with my playbutton not working and stream starting automatically, but this is a small problem and can be fixed easily I think. However, I would still be interested in knowing if there is something "wrong" with my code.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
brettsalyer
  • 57
  • 1
  • 9

1 Answers1

1

Is it conventional to do this? Or should I keep all code and methods inside of their respective Activities, or is it conventional to move them to their own class, and access them from whichever Activity needs it?

Definitely use general software engineering principles and best practices. Design classes that are focused and serve a specific purpose. You should look at the new Architecture Components library. This has tools that allow you to create "life cycle aware" classes. These classes can be responsible for handling lifecycle events for their own resources rather than polluting the Activity with managing resources.

Comment

Generally, you should not initialize fields of an Activity class outside of the activity lifecycle. This can cause problems when you depend on resources which are available only at certain times in the lifecycle, such as views. In this case, initializing the Streaming object inline with its declaration probably won't cause any problems. Still it is best to get into the habit of treating onCreate() as the "constructor" and do all initializing in this method instead.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
  • Thank you! This was exactly what I was wanting to know. So I should from now on keep all my fields (MediaPlayer, Textviews, etc) inside my onCreate? Is there usually not a need for outside classes then? At least when it comes to interaction with my TextViews and Buttons? – brettsalyer Nov 15 '17 at 22:53
  • @brettsalyer I typically keep my views in an Activity or Fragment subclass. The only exception to this that I can think of is a custom list adapter class which also needs to use views. I have not done anything with MediaPlayer, but I can see how it might be helpful to use it in another class. The key is to be sure you work inside the activity life cycle. – Code-Apprentice Nov 16 '17 at 00:27