1

I've just finished implementing my app's weather icons, now I'm trying to play weather sounds on the app based on the icons the app is displaying. After doing long hours of thorough research, I found out to my shock that no one has ever asked similar questions on any platform.

My knowledge of java dictionary is quite narrow and I find it hard to understand documentation even after taking my time to read and study their samples. i.e I found a doc I believe would correspond with what I am trying to achieve here https://docs.oracle.com/javase/8/docs/api/java/util/Map.html

It explains that

V put(K key,
      V value)

Associates the specified value with the specified key in this map. In that case, I thought of using K as Icon and V as my sound but I didn't know how to apply it so I looked for examples and saw this https://study.com/academy/lesson/the-java-dictionary-class-definition-example.html The examples it provided still wasn't enough to help me through and I know I will mess it up if I try to work it out myself, so I decided to come up with this here to see if anyone can help, I'll really appreciate it.

Following the API instructions, I'm using https://openweathermap.org/weather-conditions. There are 9 main weather conditions.

These are my goals in trying to play the sounds based on the icons:

  • If the city displays the clear_sky icon, play the clear_sky_sound
    • Otherwise, If it displays the few_clouds icon, play the few_clouds_sound
    • Otherwise, If it displays the scattered_clouds icon, play the scattered_clouds_sound
    • Otherwise, If it displays the broken_clouds, play the broken_clouds_sound
    • Otherwise, If it displays the shower_rain icon, play the shower_rain_sound
    • Otherwise, If it displays the rain icon, play the rain_sound
    • Otherwise, If it displays the thunderstorm icon, play the thunderstorm_sound
    • Otherwise, If it displays the snow icon, play the snow_sound
    • Otherwise, If it displays the mist icon, play the mist_sound.

Here is my Fragment's code:

public class FirstFragment extends Fragment {

    private WeatherDataViewModel viewModel;

    MediaPlayer firstSound, secondSound, thirdSound, fourthSound, fifthSound, sixthSound, seventhSound, eightSound, ninethSound;

    public FirstFragment() {
// Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
    // Inflate the layout for this fragment
        View rootView = inflater.inflate(R.layout.fragment_first, container, false);
    // For displaying weather data
        final TextView current_temp = rootView.findViewById(R.id.textView10);
        final TextView current_output = rootView.findViewById(R.id.textView11);
        final TextView rise_time = rootView.findViewById(R.id.textView25);
        final TextView set_time = rootView.findViewById(R.id.textView26);
        final TextView temp_out = rootView.findViewById(R.id.textView28);
        final TextView Press_out = rootView.findViewById(R.id.textView29);
        final TextView Humid_out = rootView.findViewById(R.id.textView30);
        final TextView Ws_out = rootView.findViewById(R.id.textView33);
        final TextView Visi_out = rootView.findViewById(R.id.textView34);
        final TextView Cloud_out = rootView.findViewById(R.id.textView35);
        final ImageView current_icon = rootView.findViewById(R.id.imageView6);
        final SwipeRefreshLayout realSwipe = rootView.findViewById(R.id.real_swipe);


    // Get our ViewModel instance
        viewModel = new ViewModelProvider(this).get(WeatherDataViewModel.class);



    // And whenever the data changes, refresh the UI
        viewModel.getWeatherDataLiveData().observe(getViewLifecycleOwner(), data -> {

            realSwipe.setOnRefreshListener(() -> {
                // perform you action here for ex. add refresh screen code here
                new Handler().postDelayed(() -> {
                    // this code is for stop refreshing icon, After 1000 ms automatically refresh icon will stop
                    realSwipe.setRefreshing(false);
                }, 1000);
            });

            int drawableResource = -1; // here define default icon for example R.drawable.default_weather_icon

            if (data != null) {
                current_temp.setVisibility(View.VISIBLE);
                current_temp.setText(data.getMain().getTemp() + " ℃"); // for that you can use strings resource and templates more in https://developer.android.com/guide/topics/resources/string-resource.html#formatting-strings
                current_output.setVisibility(View.VISIBLE);
                current_output.setText(data.getWeather().get(0).getDescription());
                rise_time.setVisibility(View.VISIBLE);
                rise_time.setText(data.getSys().getSunrise() + " ");
                set_time.setVisibility(View.VISIBLE);
                set_time.setText(data.getSys().getSunset() + " ");
                temp_out.setVisibility(View.VISIBLE);
                temp_out.setText(data.getMain().getTemp() + " ℃");
                Press_out.setVisibility(View.VISIBLE);
                Press_out.setText(data.getMain().getPressure() + " hpa");
                Humid_out.setVisibility(View.VISIBLE);
                Humid_out.setText(data.getMain().getHumidity() + " %");
                Ws_out.setVisibility(View.VISIBLE);
                Ws_out.setText(data.getWind().getSpeed() + " Km/h");
                Visi_out.setVisibility(View.VISIBLE);
                Visi_out.setText(data.getVisibility() + " m");
                Cloud_out.setVisibility(View.VISIBLE);
                Cloud_out.setText(data.getClouds().getAll() + " %");

// get actual weather.

                String icon = data.getWeather().get(0).getIcon();

                switch (icon) {
                    case "01d":
                    case "01n":
                        drawableResource = R.drawable.sun;

                        break;
                    case "02d":
                    case "021n":
                        drawableResource = R.drawable.few_clouds;

                        break;
                    case "03d":
                    case "03n":
                        drawableResource = R.drawable.scattered_clouds;

                        break;
                    case "04d":
                    case "04n":
                        drawableResource = R.drawable.broken_clouds;

                        break;
                    case "09d":
                    case "09n":
                        drawableResource = R.drawable.shower_rain;

                        break;
                    case "10d":
                    case "10n":
                        drawableResource = R.drawable.small_rain;

                        break;
                    case "11d":
                    case "11n":
                        drawableResource = R.drawable.thunderstorm;

                        break;
                    case "13d":
                    case "13n":
                        drawableResource = R.drawable.snow;

                        break;
                    case "50d":
                    case "50n":
                        drawableResource = R.drawable.mist;
                        break;
                }


                if (drawableResource != -1)
                    current_icon.setImageResource(drawableResource);

            } else {
                Log.e("TAG", "No City found");
                current_temp.setVisibility(View.GONE);
                current_output.setVisibility(View.GONE);
                rise_time.setVisibility(View.GONE);
                set_time.setVisibility(View.GONE);
                temp_out.setVisibility(View.GONE);
                Press_out.setVisibility(View.GONE);
                Humid_out.setVisibility(View.GONE);
                Ws_out.setVisibility(View.GONE);
                Visi_out.setVisibility(View.GONE);
                Cloud_out.setVisibility(View.GONE);
                Toast.makeText(requireActivity(), "No City found", Toast.LENGTH_SHORT).show();
            }
        });

        return rootView;
    }

    public void getWeatherData(String name) {
// The ViewModel controls loading the data, so we just
// tell it what the new name is - this kicks off loading
// the data, which will automatically call through to
// our observe() call when the data load completes
        viewModel.setCityName(name);
    }
}

EDIT:

DennisVA helped by providing a sample I could follow(i tried his 2nd suggestion) but when I tried it, I got some errors which I shared using a link in his comment section. I decided to bounty this post to draw more attention, so I can be directed more in the right direction.

Richard Wilson
  • 297
  • 4
  • 17

2 Answers2

1

MediaPlayer firstSound, secondSound, thirdSound, fourthSound, fifthSound, sixthSound, seventhSound, eightSound, ninethSound;

Here you created 9 MediaPlayer objects, and from their names and their count that exactly the same as the number of icons; I realized that each one will handle the sound of a certain icon.

Although, you could only use a single MediaPlaer object for playing any of the 9 sounds you have. Think of it like your operating system media player, you have a single media player app, and you can drag and drop any media file at a time, and change it whenever you want without changing the app itself.

This is valid as long as you want to play a single media file at a time; but, in case you want to play multiple sounds at the same time, you probably need more than one MediaPlayer object.

So, as in your case you've a single whether condition at a time, so I assume that you want to play a single file at a time; and therefore you've a single icon at a time (due to the switch statement), and hence you need to change above mentioned snippet to be a single variable:

MediaPlayer mediaPlayer;

My knowledge of java dictionary is quite narrow and I find it hard to understand documentation

A dictionary (or it's a Map for accurately speaking in java), think of it like a phone directory: You know the person name (Key), and need to get the corresponding phone number (Value); the difference in this example that in phone directory there is only a unique value for each key/person; but in java Map, you can have duplicate values (i.e. more than one key/person can own the same value/phone number).

V put(K key,
     V value)

Also, In java, the key and value can be almost anything and this can be indicated from the (K & V) symbols which are a generics.

I thought of using K as Icon and V as my sound but I didn't know how to apply it

In the shared simple example, I don't really think that you need a map; as the sound resources are highly coupled with the corresponding icon drawable resources, then you can just treat the sound raw files like you did with drawables.

So, whenever you change the current drawable (i.e. in the switch statement), you also need to change the current sound in the switch statement, assuming they have the same name as the drawables:

MediaPlayer mediaPlayer; // single MediaPlayer object

int drawableResource = -1; // here define default icon for example R.drawable.default_weather_icon
int soundResource = -1; // Default sound is nothing

String icon = data.getWeather().get(0).getIcon();
switch (icon) {
    case "01d":
    case "01n":
        drawableResource = R.drawable.sun;
        soundResource = R.raw.sun;
        break;
    case "02d":
    case "021n":
        drawableResource = R.drawable.few_clouds;
        soundResource = R.raw.few_clouds;

        break;
    case "03d":
    case "03n":
        drawableResource = R.drawable.scattered_clouds;
        soundResource = R.raw.scattered_clouds;

        break;
    case "04d":
    case "04n":
        drawableResource = R.drawable.broken_clouds;
        soundResource = R.raw.broken_clouds;

        break;
    case "09d":
    case "09n":
        drawableResource = R.drawable.shower_rain;
        soundResource = R.raw.shower_rain;

        break;
    case "10d":
    case "10n":
        drawableResource = R.drawable.small_rain;
        soundResource = R.raw.small_rain;

        break;
    case "11d":
    case "11n":
        drawableResource = R.drawable.thunderstorm;
        soundResource = R.raw.thunderstorm;

        break;
    case "13d":
    case "13n":
        drawableResource = R.drawable.snow;
        soundResource = R.raw.snow;

        break;
    case "50d":
    case "50n":
        drawableResource = R.drawable.mist;
        soundResource = R.raw.mist;
        break;
}


if (drawableResource != -1)
    current_icon.setImageResource(drawableResource);
    
    
if (soundResource != -1) {
    prepareMediaPlayer(soundResource);
}



// In FirstFragment:

private void prepareMediaPlayer(int resource) {
    // add track file
    mMediaPlayer = MediaPlayer.create(requireActivity(), resource);

    // listening to when the media file finishes playing so that we can release the resources
    mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mp) {
            if (mMediaPlayer != null) {
                mMediaPlayer.release();
            }
        }
    });
    
}

And whenever you want to play the sound, just call mMediaPlayer.start(), but make sure that prepareMediaPlayer() is called in advance.

Hint: if you want to use a map, I'd recommend that you create a POJO/Data class for the Weather, so that probably we'd a Map.

Zain
  • 37,492
  • 7
  • 60
  • 84
  • Okay, thanks a lot for your help and I'm very sorry for the late response Very tight schedule in college coded this at 2 am. My observations so far with the code: 1. Got Warning field 'mediaPlayer' is never used. 2. They weren't able to recognize the method in the firstFragment() { body, so I used it in the last side of the class, it got recognized but my public void getWeatherData(String name) { had issues with it(it affected that part) so it still didn't work. 3. Lastly, Got warning mediaPlayer != null is always true. – Richard Wilson Sep 28 '21 at 01:30
  • 1
    @RichardWilson Ooh Good luck in your college.. So, For `1` it means that you never used the `mMediaPlayer` object, so, it suggests to remove it.. I think that because the code in the provided solution is messed up with the fragment's code; so it doesn't recognized it.. I consolidated both into [here](https://pastebin.com/p8sfMM89). for `2` The same as 1; there is a mess around something like missed braces or semicolon. For `3` Just you can remove the if check.. and I already did that on the provided link – Zain Sep 28 '21 at 14:28
  • Okay, thanks a lot for your suggestion. I tested the code and this time, it showed no errors. But the sounds didn't play even after searching for cities on the app. As you said earlier on; "And whenever you want to play the sound, just call mMediaPlayer.start(), but make sure that prepareMediaPlayer() is called in advance." So I called mMediaPlayer.start() inside the method class, and it still didn't play. – Richard Wilson Sep 28 '21 at 17:01
  • @RichardWilson Can you show how you play it .. is it on a button click or something .. I just tested something similar and works; also make sure that the raw sounds are all in lower case ... The other possible thing is that you might need to request audio focus (i.e. to own the audio from the android system if something else is utilizing it) – Zain Sep 28 '21 at 19:58
  • Sure it's a button click. I search cities by typing on the edittext, then click the search button to search. I expected that when I search for a city and it has successfully displayed the icon for that city as usual, the sound will play in respect to the icons and on repeat till I search other cities. But it doesn't play at all talkless of on repeat. Sure, all the sounds are in lower case e.g clear_sky_sound; – Richard Wilson Sep 28 '21 at 22:14
  • So, when should the sound play? Is it when you hit the search button, or do you wait until the search is over and a city comes in.. I guess you call `mediaPlayer.start()` whenever there is no city yet – Zain Sep 28 '21 at 22:26
  • The latter... A city has to come in first then the sound will play in retrospect to the icon displayed for that city..... I called mMediaPlayer.start() as the last code on the sound method – Richard Wilson Sep 29 '21 at 04:55
  • @RichardWilson Could you update that in code so that I can understand more – Zain Sep 29 '21 at 05:42
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/237627/discussion-between-richard-wilson-and-zain). – Richard Wilson Sep 29 '21 at 08:00
0

I would create a wrapper to make it easier to show things to your UI

For example:

public class Weather{
   @DrawableRes
   private int icon; //Resource id of the drawable
   private String sound; //Name or path the the sound file

   public Weather(WeatherPojo pojo){
       switch(pojo.icon){
                case "01d":
                case "01n":
                    this.icon = R.drawable.sun;
                    this.sound = ...
       }

   }
}

This class could also contain a play(MediaPlayer player) method

Some other things i noticed:

Edit:

Another, probably better option would be to create some kind of WeatherFactory class or parser that produces subclasses of an abstract Weather class, and have that abstract class contain code for playing the media file:

public abstract class Weather{
     @DrawableRes
     private int iconResId;
     @RawRes 
     private int soundResId;

     protected Weather(@DrawableRes int iconResId, @RawRes int soundResId){
        this.iconResId = iconResId;
        this.soundResId = soundResId
     }

     public void playSound(MediaPlayer mediaPlayer, Context context){

        String assetFileDescriptor = context.resources.openRawResourceFd(soundResId);

        if(assetFileDescriptor == null)
             return;

        mediaPlayer.reset();
        mediaPlayer.setDataSource(assetFileDescriptor.fileDescriptor, 
        assetFileDescriptor.startOffset, assetFileDescriptor.declaredLength);
        mediaPlayer.prepareAsync();
     }
} 

public class Snow extends Weather{
      public Snow(){
          super(R.drawable.snow, R.raw.snow)
      }
}

Now u can call snow.playSound() in ur fragment or viewModel and generate getters and setters in the abstract class to get the drawable and display it.

Other variables for example the description can also be injected in the constructor of the subclass, or u can extend the subclasses themselves for the different types of snow (LightSnow, HeavySnow, ...)

Note: I haven't tested any of this code so it might need some changes here and there

DennisVA
  • 2,068
  • 1
  • 25
  • 35
  • Okay, thanks for the sample you illustrated. But please I'd appreciate it if you can also help with a sample of the mediaplayer method in the class. Also, please what do you mean by "hardcoding of strings". Okay, I'll try to engage with viewbinding later, because I've already used findviewbyid a lot, and refactoring will take a high amount of time. – Richard Wilson Sep 19 '21 at 09:29
  • with hardcoding i mean things like "℃", it's good practise to put those in fields for example private static final String TEMPERATURE_UNIT = "℃". I will try to provide a sample later whenever i find some time – DennisVA Sep 20 '21 at 04:53
  • Okay, I first want to thank you so much for your suggestion. Secondly, I'm very sorry for my slow response. I'm currently in college and my schedule is quite bulky, please bear with me. Finally, While testing the code in my app, it came out with 4 errors, 4 warnings. As you said, you haven't tested any of this code so it might need some changes here and there. I wrote the errors here for easier understanding: https://hastebin.com/dabasaqidu.typescript – Richard Wilson Sep 20 '21 at 21:09
  • Okay, I get about the hardcoding now. Looking forward to that, thanks. – Richard Wilson Sep 20 '21 at 21:10