1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.example.android.supportv7.util;
17
18import android.os.AsyncTask;
19import android.os.Bundle;
20import android.support.annotation.Nullable;
21import android.support.v4.util.Pair;
22import android.support.v7.app.AppCompatActivity;
23import android.support.v7.util.DiffUtil;
24import android.support.v7.widget.LinearLayoutManager;
25import android.support.v7.widget.RecyclerView;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.Button;
29import android.widget.LinearLayout;
30import android.widget.Toast;
31
32import com.example.android.supportv7.Cheeses;
33import com.example.android.supportv7.widget.adapter.SimpleStringAdapter;
34
35import java.util.ArrayList;
36import java.util.Collections;
37import java.util.List;
38import java.util.Random;
39import java.util.concurrent.atomic.AtomicBoolean;
40
41/**
42 * A sample activity that demonstrates usage if {@link android.support.v7.util.DiffUtil} with
43 * a RecyclerView.
44 */
45public class DiffUtilActivity extends AppCompatActivity {
46    private Random mRandom = new Random(System.nanoTime());
47
48    @Override
49    protected void onCreate(@Nullable Bundle savedInstanceState) {
50        super.onCreate(savedInstanceState);
51        LinearLayout ll = new LinearLayout(this);
52        RecyclerView rv = new RecyclerView(this);
53        Button shuffle = new Button(this);
54        shuffle.setText("Shuffle");
55        ll.addView(shuffle);
56        ll.addView(rv);
57        rv.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
58                ViewGroup.LayoutParams.MATCH_PARENT));
59        rv.setLayoutManager(new LinearLayoutManager(this));
60        List<String> cheeseList = createRandomCheeseList(Collections.<String>emptyList(), 50);
61        final SimpleStringAdapter adapter =
62                new SimpleStringAdapter(this, cheeseList.toArray(new String[cheeseList.size()]));
63        rv.setAdapter(adapter);
64        final AtomicBoolean refreshingList = new AtomicBoolean(false);
65        shuffle.setOnClickListener(new View.OnClickListener() {
66            @Override
67            public void onClick(View view) {
68                if (refreshingList.getAndSet(true)) {
69                    // already refreshing, do not allow modifications
70                    return;
71                }
72                //noinspection unchecked
73                new AsyncTask<List<String>, Void, Pair<List<String>, DiffUtil.DiffResult>>() {
74
75                    @Override
76                    protected Pair<List<String>, DiffUtil.DiffResult> doInBackground(
77                            List<String>... lists) {
78                        List<String> oldList = lists[0];
79                        List<String> newList = createRandomCheeseList(oldList, 5);
80                        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
81                                new MyCallback(oldList, newList));
82                        //noinspection unchecked
83                        return new Pair(newList, diffResult);
84                    }
85
86                    @Override
87                    protected void onPostExecute(
88                            Pair<List<String>, DiffUtil.DiffResult> resultPair) {
89                        refreshingList.set(false);
90                        adapter.setValues(resultPair.first);
91                        resultPair.second.dispatchUpdatesTo(adapter);
92                        Toast.makeText(DiffUtilActivity.this, "new list size "
93                                + resultPair.first.size(), Toast.LENGTH_SHORT).show();
94                    }
95                }.execute(adapter.getValues());
96
97            }
98        });
99        setContentView(ll);
100    }
101
102    private static class MyCallback extends DiffUtil.Callback {
103        private final List<String> mOld;
104        private final List<String> mNew;
105
106        public MyCallback(List<String> old, List<String> aNew) {
107            mOld = old;
108            mNew = aNew;
109        }
110
111        @Override
112        public int getOldListSize() {
113            return mOld.size();
114        }
115
116        @Override
117        public int getNewListSize() {
118            return mNew.size();
119        }
120
121        @Override
122        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
123            // for strings, content equality is the same as identitiy equality since we don't have
124            // duplicates in this sample.
125            return mOld.get(oldItemPosition).equals(mNew.get(newItemPosition));
126        }
127
128        @Override
129        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
130            return mOld.get(oldItemPosition).equals(mNew.get(newItemPosition));
131        }
132    }
133
134    private List<String> createRandomCheeseList(List<String> seed, int iterations) {
135        List<String> output = new ArrayList<>();
136        output.addAll(seed);
137        for (int i = 0; i < iterations; i++) {
138            switch (mRandom.nextInt(3)) {
139                case 0: //add
140                    output.add(mRandom.nextInt(1 + output.size()), getRandomCheese(output));
141                    break;
142                case 1: // remove
143                    if (output.size() > 0) {
144                        output.remove(mRandom.nextInt(output.size()));
145                    }
146                    break;
147                case 2: // move
148                    if (output.size() > 0) {
149                        int from = mRandom.nextInt(output.size());
150                        int to = mRandom.nextInt(output.size());
151                        output.add(to, output.remove(from));
152                    }
153                    break;
154            }
155        }
156        return output;
157    }
158
159    private String getRandomCheese(List<String> excludes) {
160        String chosen = Cheeses.sCheeseStrings[mRandom.nextInt(Cheeses.sCheeseStrings.length)];
161        while (excludes.contains(chosen)) {
162            chosen = Cheeses.sCheeseStrings[mRandom.nextInt(Cheeses.sCheeseStrings.length)];
163        }
164        return chosen;
165    }
166}
167