From 97149fff3f867e25103c632138468010cc804811 Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Thu, 17 Aug 2017 02:40:05 -0500 Subject: [PATCH] ObservableMapAdapter: Based on an observable TreeMap Signed-off-by: Jason A. Donenfeld --- .../android/ObservableMapAdapter.java | 111 ++++++++++++++++++ .../android/ObservableSortedMap.java | 12 ++ .../wireguard/android/ObservableTreeMap.java | 67 +++++++++++ 3 files changed, 190 insertions(+) create mode 100644 app/src/main/java/com/wireguard/android/ObservableMapAdapter.java create mode 100644 app/src/main/java/com/wireguard/android/ObservableSortedMap.java create mode 100644 app/src/main/java/com/wireguard/android/ObservableTreeMap.java diff --git a/app/src/main/java/com/wireguard/android/ObservableMapAdapter.java b/app/src/main/java/com/wireguard/android/ObservableMapAdapter.java new file mode 100644 index 00000000..d13b2b94 --- /dev/null +++ b/app/src/main/java/com/wireguard/android/ObservableMapAdapter.java @@ -0,0 +1,111 @@ +package com.wireguard.android; + +import android.content.Context; +import android.databinding.DataBindingUtil; +import android.databinding.ObservableMap; +import android.databinding.ViewDataBinding; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListAdapter; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; + +/** + * A generic ListAdapter backed by a TreeMap that adds observability. + */ + +class ObservableMapAdapter, V> extends BaseAdapter implements ListAdapter { + private ArrayList keys; + private final int layoutId; + private final LayoutInflater layoutInflater; + private ObservableSortedMap map; + private final OnMapChangedCallback callback = new OnMapChangedCallback<>(this); + + ObservableMapAdapter(final Context context, final int layoutId, + final ObservableSortedMap map) { + layoutInflater = LayoutInflater.from(context); + this.layoutId = layoutId; + setMap(map); + } + + @Override + public int getCount() { + return map != null ? map.size() : 0; + } + + @Override + public V getItem(final int position) { + if (map == null || position < 0 || position >= map.size()) + return null; + return map.get(getKeys().get(position)); + } + + @Override + public long getItemId(final int position) { + if (map == null || position < 0 || position >= map.size()) + return -1; + return getKeys().get(position).hashCode(); + } + + public int getItemPosition(final K key) { + if (map == null) + return -1; + return Collections.binarySearch(getKeys(), key); + } + + private ArrayList getKeys() { + if (keys == null) + keys = new ArrayList<>(map.keySet()); + return keys; + } + + @Override + public View getView(final int position, final View convertView, final ViewGroup parent) { + ViewDataBinding binding = DataBindingUtil.getBinding(convertView); + if (binding == null) + binding = DataBindingUtil.inflate(layoutInflater, layoutId, parent, false); + binding.setVariable(BR.item, getItem(position)); + binding.executePendingBindings(); + return binding.getRoot(); + } + + @Override + public boolean hasStableIds() { + return true; + } + + public void setMap(final ObservableSortedMap newMap) { + if (map != null) + map.removeOnMapChangedCallback(callback); + keys = null; + map = newMap; + if (map != null) { + map.addOnMapChangedCallback(callback); + } + } + + private static class OnMapChangedCallback, V> + extends ObservableMap.OnMapChangedCallback, K, V> { + + private final WeakReference> weakAdapter; + + private OnMapChangedCallback(final ObservableMapAdapter adapter) { + weakAdapter = new WeakReference<>(adapter); + } + + @Override + public void onMapChanged(final ObservableSortedMap sender, final K key) { + final ObservableMapAdapter adapter = weakAdapter.get(); + if (adapter != null) { + adapter.keys = null; + adapter.notifyDataSetChanged(); + } else { + sender.removeOnMapChangedCallback(this); + } + } + } +} diff --git a/app/src/main/java/com/wireguard/android/ObservableSortedMap.java b/app/src/main/java/com/wireguard/android/ObservableSortedMap.java new file mode 100644 index 00000000..8a642476 --- /dev/null +++ b/app/src/main/java/com/wireguard/android/ObservableSortedMap.java @@ -0,0 +1,12 @@ +package com.wireguard.android; + +import android.databinding.ObservableMap; + +import java.util.SortedMap; + +/** + * Interface for maps that are both observable and sorted. + */ + +public interface ObservableSortedMap extends ObservableMap, SortedMap { +} diff --git a/app/src/main/java/com/wireguard/android/ObservableTreeMap.java b/app/src/main/java/com/wireguard/android/ObservableTreeMap.java new file mode 100644 index 00000000..b0444d66 --- /dev/null +++ b/app/src/main/java/com/wireguard/android/ObservableTreeMap.java @@ -0,0 +1,67 @@ +package com.wireguard.android; + +import android.databinding.MapChangeRegistry; +import android.databinding.ObservableMap; +import android.support.annotation.NonNull; + +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +/** + * Observable version of a TreeMap. Only notifies for changes made through methods, not iterators or + * views. This behavior is in line with that of ObservableArrayMap. + */ + +public class ObservableTreeMap extends TreeMap implements ObservableSortedMap { + private transient MapChangeRegistry listeners; + + @Override + public void clear() { + super.clear(); + notifyChange(null); + } + + @Override + public void addOnMapChangedCallback( + final OnMapChangedCallback, K, V> listener) { + if (listeners == null) + listeners = new MapChangeRegistry(); + listeners.add(listener); + } + + private void notifyChange(final K key) { + if (listeners != null) + listeners.notifyChange(this, key); + } + + @Override + public V put(final K key, final V value) { + final V oldValue = super.put(key, value); + notifyChange(key); + return oldValue; + } + + @Override + public void putAll(@NonNull final Map map) { + super.putAll(map); + for (final K key : map.keySet()) + notifyChange(key); + } + + @Override + public V remove(final Object key) { + final V oldValue = super.remove(key); + @SuppressWarnings("unchecked") + final K k = (K) key; + notifyChange(k); + return oldValue; + } + + @Override + public void removeOnMapChangedCallback( + final OnMapChangedCallback, K, V> listener) { + if (listeners != null) + listeners.remove(listener); + } +}