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  */
16 package com.example.androidx.util;
17 
18 import android.annotation.SuppressLint;
19 import android.os.Bundle;
20 import android.view.View;
21 import android.view.ViewGroup;
22 import android.widget.Button;
23 import android.widget.LinearLayout;
24 import android.widget.Toast;
25 
26 import androidx.appcompat.app.AppCompatActivity;
27 import androidx.core.util.Pair;
28 import androidx.recyclerview.widget.DiffUtil;
29 import androidx.recyclerview.widget.LinearLayoutManager;
30 import androidx.recyclerview.widget.RecyclerView;
31 
32 import com.example.androidx.Cheeses;
33 import com.example.androidx.widget.adapter.SimpleStringAdapter;
34 
35 import org.jspecify.annotations.Nullable;
36 
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.Random;
41 import java.util.concurrent.atomic.AtomicBoolean;
42 
43 /**
44  * A sample activity that demonstrates usage if {@link androidx.recyclerview.widget.DiffUtil} with
45  * a RecyclerView.
46  */
47 public class DiffUtilActivity extends AppCompatActivity {
48     private Random mRandom = new Random(System.nanoTime());
49 
50     @Override
51     @SuppressLint("WrongThread")
52     @SuppressWarnings("deprecation") /* AsyncTask */
onCreate(@ullable Bundle savedInstanceState)53     protected void onCreate(@Nullable Bundle savedInstanceState) {
54         super.onCreate(savedInstanceState);
55         LinearLayout ll = new LinearLayout(this);
56         RecyclerView rv = new RecyclerView(this);
57         Button shuffle = new Button(this);
58         shuffle.setText("Shuffle");
59         ll.addView(shuffle);
60         ll.addView(rv);
61         rv.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
62                 ViewGroup.LayoutParams.MATCH_PARENT));
63         rv.setLayoutManager(new LinearLayoutManager(this));
64         List<String> cheeseList = createRandomCheeseList(Collections.<String>emptyList(), 50);
65         final SimpleStringAdapter adapter =
66                 new SimpleStringAdapter(this, cheeseList.toArray(new String[cheeseList.size()]));
67         rv.setAdapter(adapter);
68         final AtomicBoolean refreshingList = new AtomicBoolean(false);
69         shuffle.setOnClickListener(new View.OnClickListener() {
70             @Override
71             public void onClick(View view) {
72                 if (refreshingList.getAndSet(true)) {
73                     // already refreshing, do not allow modifications
74                     return;
75                 }
76                 new android.os.AsyncTask<List<String>, Void, Pair<List<String>,
77                         DiffUtil.DiffResult>>() {
78                     @Override
79                     protected Pair<List<String>, DiffUtil.DiffResult> doInBackground(
80                             List<String>... lists) {
81                         List<String> oldList = lists[0];
82                         List<String> newList = createRandomCheeseList(oldList, 5);
83                         DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
84                                 new MyCallback(oldList, newList));
85                         //noinspection unchecked
86                         return new Pair(newList, diffResult);
87                     }
88 
89                     @Override
90                     protected void onPostExecute(
91                             Pair<List<String>, DiffUtil.DiffResult> resultPair) {
92                         refreshingList.set(false);
93                         adapter.setValues(resultPair.first);
94                         resultPair.second.dispatchUpdatesTo(adapter);
95                         Toast.makeText(DiffUtilActivity.this, "new list size "
96                                 + resultPair.first.size(), Toast.LENGTH_SHORT).show();
97                     }
98                 }.execute(adapter.getValues());
99 
100             }
101         });
102         setContentView(ll);
103     }
104 
105     private static class MyCallback extends DiffUtil.Callback {
106         private final List<String> mOld;
107         private final List<String> mNew;
108 
MyCallback(List<String> old, List<String> aNew)109         public MyCallback(List<String> old, List<String> aNew) {
110             mOld = old;
111             mNew = aNew;
112         }
113 
114         @Override
getOldListSize()115         public int getOldListSize() {
116             return mOld.size();
117         }
118 
119         @Override
getNewListSize()120         public int getNewListSize() {
121             return mNew.size();
122         }
123 
124         @Override
areItemsTheSame(int oldItemPosition, int newItemPosition)125         public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
126             // for strings, content equality is the same as identitiy equality since we don't have
127             // duplicates in this sample.
128             return mOld.get(oldItemPosition).equals(mNew.get(newItemPosition));
129         }
130 
131         @Override
areContentsTheSame(int oldItemPosition, int newItemPosition)132         public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
133             return mOld.get(oldItemPosition).equals(mNew.get(newItemPosition));
134         }
135     }
136 
createRandomCheeseList(List<String> seed, int iterations)137     private List<String> createRandomCheeseList(List<String> seed, int iterations) {
138         List<String> output = new ArrayList<>();
139         output.addAll(seed);
140         for (int i = 0; i < iterations; i++) {
141             switch (mRandom.nextInt(3)) {
142                 case 0: //add
143                     output.add(mRandom.nextInt(1 + output.size()), getRandomCheese(output));
144                     break;
145                 case 1: // remove
146                     if (output.size() > 0) {
147                         output.remove(mRandom.nextInt(output.size()));
148                     }
149                     break;
150                 case 2: // move
151                     if (output.size() > 0) {
152                         int from = mRandom.nextInt(output.size());
153                         int to = mRandom.nextInt(output.size());
154                         output.add(to, output.remove(from));
155                     }
156                     break;
157             }
158         }
159         return output;
160     }
161 
getRandomCheese(List<String> excludes)162     private String getRandomCheese(List<String> excludes) {
163         String chosen = Cheeses.sCheeseStrings[mRandom.nextInt(Cheeses.sCheeseStrings.length)];
164         while (excludes.contains(chosen)) {
165             chosen = Cheeses.sCheeseStrings[mRandom.nextInt(Cheeses.sCheeseStrings.length)];
166         }
167         return chosen;
168     }
169 }
170