Search code examples
androidandroid-recyclerviewpicassoretrofit2openweathermap

RecyclerView overwriting list items


I'm developing a task manager app. Basically with google places api i choose the location save it to an sqlite databse and populate it in a RecyclerView. For every location i use retrofit2 and openweather api to get the current weather for that location and display it in the RecyclerView. This is done in an async task located in in the recyclerview adapter in the onBindViewHolder method. Now when i create a task it gets the data to that location with no problem. The problem starts when i add more tasks. Basically when i add a new tasks with new location it overwrites the current weather of the last added tasks like seen in the image below.

https://s9.postimg.org/l6tmi3wm7/Screenshot_20160830_143608.png

How can i make it so that it doesn't overwrites the previous tasks?

public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.ViewHolder> {

    private List<Task> taskList;

    private final String API_KEY = "8617b30a6fc114ad2ad929c111b76edf";
    private final String UNITS = "metric";
    private double latitude, longitude;
    private Task task;
    private WeatherInfo weatherInfo;
    private Context context;

    public class ViewHolder extends RecyclerView.ViewHolder {
        private TextView taskName, destination, currentWeather;

        private ImageView weatherImg;

        public ViewHolder(View view) {
            super(view);
            taskName = (TextView) view.findViewById(R.id.task);
            destination = (TextView) view.findViewById(R.id.date);
            currentWeather = (TextView) view.findViewById(R.id.weather);
            weatherImg = (ImageView) view.findViewById(R.id.weather_icon);
        }
    }

    public TaskAdapter(List<Task> taskList) {
        this.taskList = taskList;
    }

    public void add(int position, Task item) {
        taskList.add(position, item);
        notifyItemInserted(position);
    }

    public void remove(Task item) {
        int position = taskList.indexOf(item);
        taskList.remove(position);
        notifyItemRemoved(position);
    }

    public Task getTask(int position) {
        return taskList.get(position);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        context = parent.getContext();
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_rv, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        task = taskList.get(position);
        latitude = task.getDestinationLatitude();
        longitude = task.getDestinationLongitude();


        new getWeatherDataAsync(holder).execute();


        holder.taskName.setText(task.getTaskName());
        holder.destination.setText(task.getDestinationName());
    }

    @Override
    public int getItemCount() {
        return taskList.size();
    }

    private class getWeatherDataAsync extends AsyncTask<Void, Void, Void> {

        private ViewHolder holder;
        private ProgressDialog progressDialog;

        public getWeatherDataAsync(ViewHolder holder) {
            this.holder = holder;
        }

        @Override
        protected void onPreExecute() {
            progressDialog=ProgressDialog.show(context,"Loading...","Getting weather.");
        }

        @Override
        protected Void doInBackground(Void... vHolders) {
            try {

                WeatherApi weatherApi = WeatherApi.retrofit.create(WeatherApi.class);
                Call<WeatherInfo> call = weatherApi.getWeatherData(latitude, longitude, API_KEY, UNITS);
                weatherInfo = call.execute().body();

//
            } catch (IOException e) {
                Log.e("get weather coordinates", "something went wrong: " + e.getMessage());
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);

            if (weatherInfo != null) {
                holder.currentWeather.setText(String.valueOf(weatherInfo.getMain().getTemp()) + "\u2103");
                getWeatherIcon(holder);

            } else {
                holder.currentWeather.setText("N/A \u2103");
                Picasso.with(context).load("file:///android_asset/md-weather-iconset/weather-none-available.png").into(holder.weatherImg);
            }


            progressDialog.dismiss();

        }

    }

    /**
     * Display the correct weather icon from assets based on the data returned from the Retrofit query.
     * @param viewHolder
     */
    private void getWeatherIcon(ViewHolder viewHolder){
         String base="file:///android_asset/md-weather-iconset";



        switch (weatherInfo.getWeather().get(0).getIcon()) {
            case "01d":
                Picasso.with(context).load(base+"/weather-clear.png").into(viewHolder.weatherImg);
                break;
            case "02d":
                Picasso.with(context).load(base+"/weather-few-clouds.png").into(viewHolder.weatherImg);
                break;
            case "03d":
                Picasso.with(context).load(base+"/weather-clouds.png").into(viewHolder.weatherImg);
                break;
            case "04d":
                Picasso.with(context).load(base+"/weather-clouds.png").into(viewHolder.weatherImg);
                break;
            case "09d":
                Picasso.with(context).load(base+"/weather-showers-day.png").into(viewHolder.weatherImg);
                break;
            case "10d":
                Picasso.with(context).load(base+"/weather-rain-day.png").into(viewHolder.weatherImg);
                break;
            case "11d":
                Picasso.with(context).load(base+"/weather-storm-day.png").into(viewHolder.weatherImg);
                break;
            case "13d":
                Picasso.with(context).load(base+"/weather-snow.png").into(viewHolder.weatherImg);
                break;
            case "50d":
                Picasso.with(context).load(base+"/weather-mist.png").into(viewHolder.weatherImg);
                break;
            case "01n":
                Picasso.with(context).load(base+"/weather-clear-night.png").into(viewHolder.weatherImg);
                break;
            case "02n":
                Picasso.with(context).load(base+"/weather-few-clouds-night.png").into(viewHolder.weatherImg);
                break;
            case "03n":
                Picasso.with(context).load(base+"/weather-clouds-night.png").into(viewHolder.weatherImg);
                break;
            case "04n":
                Picasso.with(context).load(base+"/weather-clouds-night.png").into(viewHolder.weatherImg);
                break;
            case "09n":
                Picasso.with(context).load(base+"/weather-showers-night.png").into(viewHolder.weatherImg);
                break;
            case "10n":
                Picasso.with(context).load(base+"/weather-rain-night.png").into(viewHolder.weatherImg);
                break;
            case "11n":
                Picasso.with(context).load(base+"/weather-storm-night.png").into(viewHolder.weatherImg);
                break;
            case "13n":
                Picasso.with(context).load(base+"/weather-snow.png").into(viewHolder.weatherImg);
                break;
            case "50n":
                Picasso.with(context).load(base+"/weather-mist.png").into(viewHolder.weatherImg);
                break;


        }
    }


}

Solution

  • You are using onBindViewHolder() to start async task and passing reference to holder to it. Unfortunately, that's not how RecyclerView is supposed to work. RecyclerView uses ViewHolder pattern to reuse already instantiated layouts that are no longer visible, that's why holder may represent completely different item at the time network response kicks in.

    What you should do, is to move invoking async call somewhere else (e.g. Fragment/Activity/Presenter), update your taskList collection from callback and notify adapter that dataset has changed using one of following methods:

    notifyDataSetChanged()

    notifyItemInserted()

    Take a look at RecyclerView.Adapter docs:

    ReyclerView.Adapter