0

I have a weather app that allows the user to save locations (Stored in a room DB locally) and display the weather of said locations. When the user adds a city to the db, i perform the weather API call then add the city to the database with the info needed.

public class InsertSingleCityWorker extends Worker {

private MyCitiesDatabase citiesDatabase;
private String nickname;
private int zip;

private static final String TAG = "APICallsWorker";


public InsertSingleCityWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
    super(context, workerParams);
    citiesDatabase = MyCitiesDatabase.getInstance(getApplicationContext());
    Log.d(TAG, "APICallsWorker: WORK CREATED");
}

@NonNull
@Override
public Result doWork() {
    nickname = getInputData().getString("nickname");
    zip = getInputData().getInt("zip", -1);
    performAPICalls();
    return Result.success();
}

private void performAPICalls() {
    WeatherApi weatherApi = RetrofitService.createService(WeatherApi.class);
    weatherApi.getWeatherWithZip(zip, Constants.API_KEY).enqueue(new Callback<WeatherResponse>() {
        @Override
        public void onResponse(Call<WeatherResponse> call, Response<WeatherResponse> response) {
            if(response.isSuccessful()){
                if(response.body() != null){
                    handleAPIResult(response.body());
                }
            } else{
                Toast.makeText(getApplicationContext(), "Please Enter a valid zip", Toast.LENGTH_SHORT).show();
            }
        }
        @Override
        public void onFailure(Call<WeatherResponse> call, Throwable t) {
            handleError(t);
        }
    });
}

private void handleAPIResult(WeatherResponse weatherResponse) {
    Log.d(TAG, "handleAPIResult: HERE");
    Completable.fromAction(() -> {
        String timestamp = StringManipulation.getCurrentTimestamp();
        int temperature = Integer.valueOf(Conversions.kelvinToFahrenheit(weatherResponse.getMain().getTemp()));
        String locationName = weatherResponse.getName();
        int windSpeed = Math.round(weatherResponse.getWind().getSpeed());
        int humidity = Math.round(weatherResponse.getMain().getHumidity());

        MyCity city = new MyCity(nickname, zip, timestamp, temperature, locationName, windSpeed, humidity);
        citiesDatabase.myCitiesDao().insert(city);
    }).observeOn(AndroidSchedulers.mainThread())
            .subscribeOn(Schedulers.io())
            .subscribe();
}

private void handleError(Throwable t) {
    Log.d(TAG, "handleError: ERROR: " + t.getMessage());
}

}

Right now i have a button in the UI to update all cities at once (eventually i want it to be a simple scroll down to update or something like that). The problem is that when i click update all cities, the work is performed but the recyclerview is not update (because new data is not passed to it) but if i change the configuration (like rotating the screen) the new data will display.

Here is my main class:

public class MainActivity extends AppCompatActivity implements AddCityDialog.AddCityDialogListener {

    private static final String TAG = "MainActivity";

    //Widgets
    private Button mAddNewCity;

    private RecyclerView mRecyclerView;
    private WeatherRecyclerAdapter mRecyclerAdapter;

    private ViewAddDeleteCitiesViewModel mViewDeleteViewModel;
    private ViewAddDeleteCitiesViewModelFactory mViewDeleteMyCitiesViewModelFactory;

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

        initWidgets();
        initViewModel();
        initRecyclerView();
        setupListeners();
    }


    public void initWidgets() {
        mAddNewCity = findViewById(R.id.btn_add_new_city);
    }

    public void initViewModel() {
        mViewDeleteMyCitiesViewModelFactory = new ViewAddDeleteCitiesViewModelFactory(this.getApplication());
        mViewDeleteViewModel = new ViewModelProvider(this, mViewDeleteMyCitiesViewModelFactory).get(ViewAddDeleteCitiesViewModel.class);

        mViewDeleteViewModel.observeAllCities().observe(this, cities -> mRecyclerAdapter.submitList(cities));
    }


    public void setupListeners() {
        mAddNewCity.setOnClickListener(v -> addCityDialog());

        mRecyclerAdapter.setOnItemClickListener(myCity -> Toast.makeText(MainActivity.this, "Item Clicked: ", Toast.LENGTH_SHORT).show());

        new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
            @Override
            public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
                return false;
            }

            @Override
            public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
                mViewDeleteViewModel.delete(mRecyclerAdapter.getCityAt(viewHolder.getAdapterPosition()));
                Log.d(TAG, "onSwiped: Deleted");
            }
        }).attachToRecyclerView(mRecyclerView);
    }

    public void addCityDialog() {
        AddCityDialog addCityDialog = new AddCityDialog();
        addCityDialog.show(getSupportFragmentManager(), "Add City Dialog");
    }

    public void initRecyclerView() {
        mRecyclerView = findViewById(R.id.recycler_view);
        mRecyclerAdapter = new WeatherRecyclerAdapter();
        mRecyclerView.setAdapter(mRecyclerAdapter);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        Log.d(TAG, "initRecyclerView: Recycler View Initialized");
    }

    /**
     * Uses information (@param nickname @param zip) from Dialog to add a new city to the room db
     * by passing the input to the one time worker class.
     * @param nickname
     * @param zip
     */
    @Override
    public void addCity(String nickname, String zip) {
        insertSingleCity(nickname, Integer.valueOf(zip));
        Log.d(TAG, "addCity: CITY ADDED MAIN ACTIVITY");

        updateAllCities(mViewDeleteViewModel.getAllCities());
    }

    public void insertSingleCity(String nickname, int zip) {
        OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(InsertSingleCityWorker.class)
                .setInputData(DataManipulation.createInputData(nickname, zip))
                .build();

        WorkManager.getInstance(this).enqueue(oneTimeWorkRequest);
    }


    public void updateCities(View v){
        updateAllCities(mViewDeleteViewModel.getAllCities());
    }


    //Stars work to get API when a city is added
    private void updateAllCities(List<MyCity> cities) {
        Log.d(TAG, "startWork: here");
        for (MyCity city : cities) {
            OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(UpdateAllCitiesWorker.class)
                    .setInputData(DataManipulation.createInputData(city.getId(), city.getNickname(), city.getZipCode()))
                    .build();

            WorkManager.getInstance(this).enqueue(oneTimeWorkRequest);
            Log.d(TAG, "startWork: WORK ENQUEUED");
        }
    }
}

Update all cities worker:

    public UpdateAllCitiesWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
    super(context, workerParams);
    citiesDatabase = MyCitiesDatabase.getInstance(getApplicationContext());
    Log.d(TAG, "APICallsWorker: WORK CREATED");
}

@NonNull
@Override
public Result doWork() {
    id = getInputData().getLong("id", -1);
    nickname = getInputData().getString("nickname");
    zip = getInputData().getInt("zip", -1);
    performAPICalls();
    return Result.success();
}

private void performAPICalls() {

    WeatherApi weatherApi = RetrofitService.createService(WeatherApi.class);


    weatherApi.getWeatherWithZip(zip, Constants.API_KEY).enqueue(new Callback<WeatherResponse>() {
        @Override
        public void onResponse(Call<WeatherResponse> call, Response<WeatherResponse> response) {
            if (response.isSuccessful()) {
                if (response.body() != null) {
                    handleAPIResult(response.body());
                }
            }
        }

        @Override
        public void onFailure(Call<WeatherResponse> call, Throwable t) {
            handleError(t);
        }
    });


}

private void handleAPIResult(WeatherResponse weatherResponse) {
    Log.d(TAG, "handleAPIResult: HERE");
    Completable.fromAction(() -> {
        String timestamp = StringManipulation.getCurrentTimestamp();
        int temperature = Integer.valueOf(Conversions.kelvinToFahrenheit(weatherResponse.getMain().getTemp()));
        String locationName = weatherResponse.getName();
        int windSpeed = Math.round(weatherResponse.getWind().getSpeed());
        int humidity = Math.round(weatherResponse.getMain().getHumidity());

        MyCity city = new MyCity(nickname, zip, timestamp, temperature, locationName, windSpeed, humidity);
        city.setId(id);
        citiesDatabase.myCitiesDao().update(city);
        Log.d(TAG, "run: city added");
    }).observeOn(AndroidSchedulers.mainThread())
            .subscribeOn(Schedulers.io())
            .subscribe();

}

private void handleError(Throwable t) {
    Log.d(TAG, "handleError: here");
}

}

And the dao:

  @Dao
public interface MyCitiesDao {

    @Insert
    void insert(MyCity myCity);

    @Query("SELECT * FROM cities ORDER BY id ASC")
    LiveData<List<MyCity>> observeAllCities();

    @Query("SELECT * FROM cities ORDER BY id ASC")
    List<MyCity> getAllCities();

    @Delete
    void delete(MyCity... city);

    @Update
    void update(MyCity... city);
}

Here is what the app looks like

Deyvidoes
  • 1
  • 1

1 Answers1

0

I think the real problem is because you're calling submitList because according to docs

When you call submitList it submits a new list to be diffed and displayed.

This is why whenever you call submitList on the previous (already submitted list), it does not calculate the Diff and does not notify the adapter for change in the dataset.

A solution might be use the following approach (Not very fancy in my opinion) but should be working.

submitList(null);
submitList(cities);

Another solution would be to override submitList in your adapter class

@Override
public void submitList(final List<MyCity> list) {
    super.submitList(list != null ? new ArrayList<>(list) : null);
}

A third solution would be to create a copy of the list as below

submitList(new ArrayList(cities))

Please take a look at this stackoverflow question for more information source

  • this wasn't the solution but it got me thinking in the right direction. The observer submits a list every time anything changes in the list. The problem was, in my diff callback, i was only checking for nickname and zip to check if the contents were the same instead of also checking for the timestamp. Adding the timestamp solved all my issues. Unbelievable. – Deyvidoes Mar 10 '20 at 17:28
  • Great, I'm happy to help you – Oscar Emilio Perez Martinez Mar 10 '20 at 17:29