1
  • What I'm trying to do : I am trying to show a list of cities in a RecyclerView in the MainActivity and whenever a city in the list is clicked a WeatherDetailsActivity should fetch a response from the OpenWeather's API using Retrofit. I'm using MVVM architecture.
  • What I've managed to do so far : I've got the list showing up perfectly and even the response is fetched properly according to a POJO model, but when I click on a city it crashes due to NullPointerException.
  • TL;DR : I don't understand why the return weatherResult; in the WeatherRepository.java always return null.

  • Code:

WeatherRepository.java


public class WeatherRepository {
    final String TAG = this.getClass()
                           .getSimpleName();

    private static final String WEATHER_API_URL = "https://api.openweathermap.org/";
    private static WeatherRepository weatherRepository;
    private APIService apiService;
    MutableLiveData weatherResult;


    private WeatherRepository() {
        Retrofit retrofit = new Retrofit.Builder().baseUrl(WEATHER_API_URL)
                                                  .addConverterFactory(GsonConverterFactory.create())
                                                  .build();
        apiService = retrofit.create(APIService.class);
    }

    public synchronized static WeatherRepository getInstance() {
        if (weatherRepository == null) {
            return new WeatherRepository();
        }
        return weatherRepository;
    }

    public MutableLiveData<WeatherResult> getWeatherData(String city, String appid) {
        apiService.getWeatherData(city, appid)
                  .enqueue(
                          new Callback<WeatherResult>() {
                              @Override
                              public void onResponse(Call<WeatherResult> call, Response<WeatherResult> response) {

                                  if (response.isSuccessful()) {
                                      Log.i(TAG, "onResponse: " + response.body()
                                                                          .toString());
                                      //response.body() gets the value perfectly.
                                      weatherResult.setValue(response.body());
                                  }
                              }
                              @Override
                              public void onFailure(Call<WeatherResult> call, Throwable t) {
                                  t.printStackTrace();
                                  Log.d(TAG, "onFailure: " + t.getMessage());
                              }
                          });

        return weatherResult; //this always return null
    }
}



WeatherViewModel.java

public class WeatherViewModel extends ViewModel
{
    private String TAG = this.getClass().getSimpleName();
     private MutableLiveData<WeatherResult> data = new MutableLiveData<>();


    private String APP_ID = /*secret api key*/;


    public void getWeather(String city)
    {
        data.setValue(WeatherRepository.getInstance().getWeatherData(city,APP_ID).getValue()); // this line throws NullPointerException.
        Log.d(TAG, "WeatherViewModel: "+(data.toString()));
    }

    public LiveData<WeatherResult> getCityDetails()
    {
        return data;
    }

}


APIService.java

public interface APIService {

    //Weather API
    //http://samples.openweathermap.org/data/2.5/weather?q=London&appid={APP_ID}

    @GET("data/2.5/weather?")
    Call<WeatherResult> getWeatherData(@Query("q") String city,@Query("appid") String appID);

}

WeatherDetails.java

public class WeatherDetails extends AppCompatActivity {

    WeatherViewModel viewModel;
    TextView textView;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.weather_details);

        textView = findViewById(R.id.weather_info_text);
        viewModel = ViewModelProviders.of(this).get(WeatherViewModel.class);
    }

    @Override
    protected void onStart() {
        super.onStart();
        viewModel.getCityDetails().observe(this, new Observer<WeatherResult>() {
            @Override
            public void onChanged(WeatherResult weatherResult) {
                textView.setText(weatherResult.toString()); //this line throws NullPointerException
            }
        });
    }
}

All I want is to return the Retrofit response and set it into the MutableLiveData<WeatherResult> so that it is observed inside the WeatherDetails.java and the UI is updated accordingly.

I don't get why the return weatherResult; in the WeatherRepository.java always returns null

Now I'm not sure how to proceed, I'm at the end of the ropes so I'm asking it here.

Incase you want to look at the entire code here is the code on github. Thank you for every and any help.


UPDATE I did the following changes to resolve NullPointerException and the crash is fixed. link to diff


UPDATE 2 Here is the final working code, what did it for me was to get rid of a MutableLiveData in my ViewModel class and instead use a public method in my ViewModel as a wrapper to call the repository service call to the API server. Here is the link to the diff. Thank you to everyone who helped.

SandyG
  • 33
  • 1
  • 5
  • enqueue always runs on a background thread and return works before the task has executed – Kartik Jan 16 '20 at 12:15
  • Ok so I am aware of .execute() method for Call in retrofit will that help? If not are you aware of any solutions? – SandyG Jan 16 '20 at 12:21
  • 1
    `weatherResult` should be initialized in the method itself, and should be a local variable, and not kept as a field variable of `WeatherRepository`. – EpicPandaForce Jan 16 '20 at 12:41
  • Thank You for your response. So I tried this https://pastebin.com/SMMSvhHM like you said but I got this https://pastebin.com/WZwnnfgN – SandyG Jan 16 '20 at 13:00
  • 1
    Where are you calling WeatherViewMode.getWeather() to trigger the process? You're liveData's value is initialized as null (new MutableLiveData<>()) so it's to expect the very first time it will be null until getWeather() refreshes it. – Jorge Cevallos Jan 16 '20 at 13:07
  • @JorgeCevallos It is in the MainActivity:onRecyclerViewElementClick here is the code to it https://pastebin.com/fUCRPZmQ – SandyG Jan 16 '20 at 13:15
  • 1
    Don't re-start the activity after every click. The main idea of liveData/Observable is to get data reactively. The Observer will be called every time so you can update the UI. And try ignoring weatherResult when it's null the first time. Check if it stops being null after you click an item (and not re-start the activity). – Jorge Cevallos Jan 16 '20 at 13:27
  • @JorgeCevallos Thank you, I'll try that and update here. – SandyG Jan 16 '20 at 13:47
  • You'd need to call back asynchronously instead of returning synchronously. – Martin Zeitler Jan 16 '20 at 17:44

0 Answers0