1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package androidx.leanback.widget; 15 16 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; 17 18 import android.annotation.SuppressLint; 19 import android.database.Observable; 20 21 import androidx.annotation.RestrictTo; 22 23 import org.jspecify.annotations.NonNull; 24 import org.jspecify.annotations.Nullable; 25 26 /** 27 * Base class adapter to be used in leanback activities. Provides access to a data model and is 28 * decoupled from the presentation of the items via {@link PresenterSelector}. 29 */ 30 public abstract class ObjectAdapter { 31 32 /** Indicates that an id has not been set. */ 33 public static final int NO_ID = -1; 34 35 /** 36 * A DataObserver can be notified when an ObjectAdapter's underlying data 37 * changes. Separate methods provide notifications about different types of 38 * changes. 39 */ 40 public static abstract class DataObserver { 41 /** 42 * Called whenever the ObjectAdapter's data has changed in some manner 43 * outside of the set of changes covered by the other range-based change 44 * notification methods. 45 */ onChanged()46 public void onChanged() { 47 } 48 49 /** 50 * Called when a range of items in the ObjectAdapter has changed. The 51 * basic ordering and structure of the ObjectAdapter has not changed. 52 * 53 * @param positionStart The position of the first item that changed. 54 * @param itemCount The number of items changed. 55 */ onItemRangeChanged(int positionStart, int itemCount)56 public void onItemRangeChanged(int positionStart, int itemCount) { 57 onChanged(); 58 } 59 60 /** 61 * Called when a range of items in the ObjectAdapter has changed. The 62 * basic ordering and structure of the ObjectAdapter has not changed. 63 * 64 * @param positionStart The position of the first item that changed. 65 * @param itemCount The number of items changed. 66 * @param payload Optional parameter, use null to identify a "full" update. 67 */ onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload)68 public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { 69 onChanged(); 70 } 71 72 /** 73 * Called when a range of items is inserted into the ObjectAdapter. 74 * 75 * @param positionStart The position of the first inserted item. 76 * @param itemCount The number of items inserted. 77 */ onItemRangeInserted(int positionStart, int itemCount)78 public void onItemRangeInserted(int positionStart, int itemCount) { 79 onChanged(); 80 } 81 82 /** 83 * Called when an item is moved from one position to another position 84 * 85 * @param fromPosition Previous position of the item. 86 * @param toPosition New position of the item. 87 */ onItemMoved(int fromPosition, int toPosition)88 public void onItemMoved(int fromPosition, int toPosition) { 89 onChanged(); 90 } 91 92 /** 93 * Called when a range of items is removed from the ObjectAdapter. 94 * 95 * @param positionStart The position of the first removed item. 96 * @param itemCount The number of items removed. 97 */ onItemRangeRemoved(int positionStart, int itemCount)98 public void onItemRangeRemoved(int positionStart, int itemCount) { 99 onChanged(); 100 } 101 } 102 103 private static final class DataObservable extends Observable<DataObserver> { 104 DataObservable()105 DataObservable() { 106 } 107 notifyChanged()108 public void notifyChanged() { 109 for (int i = mObservers.size() - 1; i >= 0; i--) { 110 mObservers.get(i).onChanged(); 111 } 112 } 113 notifyItemRangeChanged(int positionStart, int itemCount)114 public void notifyItemRangeChanged(int positionStart, int itemCount) { 115 for (int i = mObservers.size() - 1; i >= 0; i--) { 116 mObservers.get(i).onItemRangeChanged(positionStart, itemCount); 117 } 118 } 119 notifyItemRangeChanged(int positionStart, int itemCount, Object payload)120 public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) { 121 for (int i = mObservers.size() - 1; i >= 0; i--) { 122 mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload); 123 } 124 } 125 notifyItemRangeInserted(int positionStart, int itemCount)126 public void notifyItemRangeInserted(int positionStart, int itemCount) { 127 for (int i = mObservers.size() - 1; i >= 0; i--) { 128 mObservers.get(i).onItemRangeInserted(positionStart, itemCount); 129 } 130 } 131 notifyItemRangeRemoved(int positionStart, int itemCount)132 public void notifyItemRangeRemoved(int positionStart, int itemCount) { 133 for (int i = mObservers.size() - 1; i >= 0; i--) { 134 mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); 135 } 136 } 137 notifyItemMoved(int positionStart, int toPosition)138 public void notifyItemMoved(int positionStart, int toPosition) { 139 for (int i = mObservers.size() - 1; i >= 0; i--) { 140 mObservers.get(i).onItemMoved(positionStart, toPosition); 141 } 142 } 143 hasObserver()144 boolean hasObserver() { 145 return mObservers.size() > 0; 146 } 147 } 148 149 private final DataObservable mObservable = new DataObservable(); 150 private boolean mHasStableIds; 151 private PresenterSelector mPresenterSelector; 152 153 /** 154 * Constructs an adapter with the given {@link PresenterSelector}. 155 */ ObjectAdapter(@onNull PresenterSelector presenterSelector)156 public ObjectAdapter(@NonNull PresenterSelector presenterSelector) { 157 setPresenterSelector(presenterSelector); 158 } 159 160 /** 161 * Constructs an adapter that uses the given {@link Presenter} for all items. 162 */ ObjectAdapter(@onNull Presenter presenter)163 public ObjectAdapter(@NonNull Presenter presenter) { 164 setPresenterSelector(new SinglePresenterSelector(presenter)); 165 } 166 167 /** 168 * Constructs an adapter. 169 */ ObjectAdapter()170 public ObjectAdapter() { 171 } 172 173 /** 174 * Sets the presenter selector. May not be null. 175 */ setPresenterSelector(@onNull PresenterSelector presenterSelector)176 public final void setPresenterSelector(@NonNull PresenterSelector presenterSelector) { 177 if (presenterSelector == null) { 178 throw new IllegalArgumentException("Presenter selector must not be null"); 179 } 180 final boolean update = (mPresenterSelector != null); 181 final boolean selectorChanged = update && mPresenterSelector != presenterSelector; 182 183 mPresenterSelector = presenterSelector; 184 185 if (selectorChanged) { 186 onPresenterSelectorChanged(); 187 } 188 if (update) { 189 notifyChanged(); 190 } 191 } 192 193 /** 194 * Called when {@link #setPresenterSelector(PresenterSelector)} is called 195 * and the PresenterSelector differs from the previous one. 196 */ onPresenterSelectorChanged()197 protected void onPresenterSelectorChanged() { 198 } 199 200 /** 201 * Returns the presenter selector for this ObjectAdapter. 202 */ getPresenterSelector()203 public final @NonNull PresenterSelector getPresenterSelector() { 204 return mPresenterSelector; 205 } 206 207 /** 208 * Registers a DataObserver for data change notifications. 209 */ registerObserver(@onNull DataObserver observer)210 public final void registerObserver(@NonNull DataObserver observer) { 211 mObservable.registerObserver(observer); 212 } 213 214 /** 215 * Unregisters a DataObserver for data change notifications. 216 */ unregisterObserver(@onNull DataObserver observer)217 public final void unregisterObserver(@NonNull DataObserver observer) { 218 mObservable.unregisterObserver(observer); 219 } 220 221 /** 222 */ 223 @RestrictTo(LIBRARY_GROUP_PREFIX) hasObserver()224 public final boolean hasObserver() { 225 return mObservable.hasObserver(); 226 } 227 228 /** 229 * Unregisters all DataObservers for this ObjectAdapter. 230 */ unregisterAllObservers()231 public final void unregisterAllObservers() { 232 mObservable.unregisterAll(); 233 } 234 235 /** 236 * Notifies UI that some items has changed. 237 * 238 * @param positionStart Starting position of the changed items. 239 * @param itemCount Total number of items that changed. 240 */ notifyItemRangeChanged(int positionStart, int itemCount)241 public final void notifyItemRangeChanged(int positionStart, int itemCount) { 242 mObservable.notifyItemRangeChanged(positionStart, itemCount); 243 } 244 245 /** 246 * Notifies UI that some items has changed. 247 * 248 * @param positionStart Starting position of the changed items. 249 * @param itemCount Total number of items that changed. 250 * @param payload Optional parameter, use null to identify a "full" update. 251 */ notifyItemRangeChanged( int positionStart, int itemCount, @Nullable Object payload )252 public final void notifyItemRangeChanged( 253 int positionStart, 254 int itemCount, 255 @Nullable Object payload 256 ) { 257 mObservable.notifyItemRangeChanged(positionStart, itemCount, payload); 258 } 259 260 /** 261 * Notifies UI that new items has been inserted. 262 * 263 * @param positionStart Position where new items has been inserted. 264 * @param itemCount Count of the new items has been inserted. 265 */ notifyItemRangeInserted(int positionStart, int itemCount)266 final protected void notifyItemRangeInserted(int positionStart, int itemCount) { 267 mObservable.notifyItemRangeInserted(positionStart, itemCount); 268 } 269 270 /** 271 * Notifies UI that some items that has been removed. 272 * 273 * @param positionStart Starting position of the removed items. 274 * @param itemCount Total number of items that has been removed. 275 */ notifyItemRangeRemoved(int positionStart, int itemCount)276 final protected void notifyItemRangeRemoved(int positionStart, int itemCount) { 277 mObservable.notifyItemRangeRemoved(positionStart, itemCount); 278 } 279 280 /** 281 * Notifies UI that item at fromPosition has been moved to toPosition. 282 * 283 * @param fromPosition Previous position of the item. 284 * @param toPosition New position of the item. 285 */ notifyItemMoved(int fromPosition, int toPosition)286 protected final void notifyItemMoved(int fromPosition, int toPosition) { 287 mObservable.notifyItemMoved(fromPosition, toPosition); 288 } 289 290 /** 291 * Notifies UI that the underlying data has changed. 292 */ notifyChanged()293 final protected void notifyChanged() { 294 mObservable.notifyChanged(); 295 } 296 297 /** 298 * Returns true if the item ids are stable across changes to the 299 * underlying data. When this is true, clients of the ObjectAdapter can use 300 * {@link #getId(int)} to correlate Objects across changes. 301 */ 302 @SuppressLint("KotlinPropertyAccess") hasStableIds()303 public final boolean hasStableIds() { 304 return mHasStableIds; 305 } 306 307 /** 308 * Sets whether the item ids are stable across changes to the underlying 309 * data. 310 */ setHasStableIds(boolean hasStableIds)311 public final void setHasStableIds(boolean hasStableIds) { 312 boolean changed = mHasStableIds != hasStableIds; 313 mHasStableIds = hasStableIds; 314 315 if (changed) { 316 onHasStableIdsChanged(); 317 } 318 } 319 320 /** 321 * Called when {@link #setHasStableIds(boolean)} is called and the status 322 * of stable ids has changed. 323 */ onHasStableIdsChanged()324 protected void onHasStableIdsChanged() { 325 } 326 327 /** 328 * Returns the {@link Presenter} for the given item from the adapter. 329 */ getPresenter(@onNull Object item)330 public final @Nullable Presenter getPresenter(@NonNull Object item) { 331 if (mPresenterSelector == null) { 332 throw new IllegalStateException("Presenter selector must not be null"); 333 } 334 return mPresenterSelector.getPresenter(item); 335 } 336 337 /** 338 * Returns the number of items in the adapter. 339 */ size()340 public abstract int size(); 341 342 /** 343 * Returns the item for the given position. 344 */ get(int position)345 public abstract @Nullable Object get(int position); 346 347 /** 348 * Returns the id for the given position. 349 */ getId(int position)350 public long getId(int position) { 351 return NO_ID; 352 } 353 354 /** 355 * Returns true if the adapter pairs each underlying data change with a call to notify and 356 * false otherwise. 357 */ isImmediateNotifySupported()358 public boolean isImmediateNotifySupported() { 359 return false; 360 } 361 } 362