如何使用数据绑定库将 Firebase 侦听器转换为可观察地图?

How can I turn Firebase listeners into observable map using data binding library?

TL;博士

是否可以隐藏所有Firebase read and write operations behind a ObservableMap as a Facade Pattern

所以我们要做的就是:

User oldUser = map.put(user);
User newUser = map.get(primaryKey);

完整问题

根据Firebase documentation, in order to write a value, I would have to define a resource path through aDatabaseReference设置一个值。

例如,如果我们有一个 User 纯文本对象,我们会将其设置为:

mDatabase.child("users")
    .push()
    .setValue(user);

为了读取整个 users 树,我们必须实现 ChildEventListener. As soon as the new user be part of the tree, it would be received through onChildAdded:

@Override
public void onChildChanged (DataSnapshot snapshot, String previousChildName) {
    Log.i(TAG, snapshot.getValue(User.class));
}

最后,为了读取特定用户,我们使用 ValueEventListener:

mDatabase.child("users")
    .child(primaryKey)
    .setValue(user)
    .addValueEventListener(new ValueEventListener() {
        @Override
            public void onDataChange(DataSnapshot snapshot) {
            Log.i(TAG, snapshot.getValue(User.class));
        }

        @Override
        public void onCancelled(DatabaseError error) {
            Log.w(TAG, "loadPost:onCancelled", error.getMessage());
        }
    });

那么可以使用 ObservableMap as a Facade Pattern, hidding all Firebase read and write operations 吗?

TL;博士

我合并了ObservableArrayMap and ChildEventListener into a FirebaseArrayMap.

工作 FirebaseUI-Android example is available here。现在您所要做的就是:

// Remote updates immediately send you your view
map.addOnMapChangedCallback(mUserChangedCallback);

// Non blocking operation to update database
map.create(user);

// Local up-to-date cache
User user = map.get(primaryKey);

记得(取消)注册您的 OnMapChangedCallback in onResume and onPause in order to avoid memory leaking caused by ChildEventListener 远程更新。

增删改查

首先我们需要为Map创建一个同义词库接口,即:

public interface CRUD<K, V> {

    Task<V> create(V value);

    Task<V> create(K key, V value);

    Task<Void> createAll(SimpleArrayMap<? extends K, ? extends V> array);

    Task<Void> createAll(Map<? extends K, ? extends V> map);

    V read(K key);

    Task<V> delete(K key);

    Task<Void> free();

}

此接口将由 FirebaseArrayMap 实现,返回 Map 相同的值 ].

这些方法类似于 put, putAll and get, but instead of return values they return Tasks 的那些值(或例外)。示例:

    @Override
    public Task<Void> createAll(Map<? extends K, ? extends V> map) {
        Collection<Task<V>> tasks = new ArrayList<>(map.size());
        for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
            tasks.add(create(entry.getKey(), entry.getValue()));
        }
        return Tasks.whenAll(tasks);
    }

    @Override
    public V read(K key) {
        return get(key);
    }

    @Override
    public Task<V> delete(K key) {
        final V oldValue = get(key);
        final Continuation<Void, V> onDelete = new Continuation<Void, V>() {
            @Override
            public V then(@NonNull Task<Void> task) throws Exception {
                task.getResult();
                return oldValue;
            }
        };
        return mDatabaseReference.child(key.toString())
            .setValue(null)
            .continueWith(onDelete);
    }

ObservableArrayMap

我们创建一个摘要FirebaseArrayMap extending ObservableArrayMap and implementing both ChildEventListener and CRUD

public abstract class FirebaseArrayMap<K extends Object, V> extends
    ObservableArrayMap<K, V> implements ChildEventListener, CRUD<K, V> {

    private final DatabaseReference mDatabaseReference;

    public abstract Class<V> getType();

    public FirebaseArrayMap(@NonNull DatabaseReference databaseReference) {
        mDatabaseReference = databaseReference;
    }

ChildEventListener will use the super methods, turning the ObservableArrayMap放入本地缓存。

因此,当写入操作成功完成(或发生远程更改)时,ChildEventListener 将自动更新我们的 Map

    @Override
    public void onCancelled(DatabaseError error) {
        Log.e(TAG, error.getMessage(), error.toException());
    }

    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChildName) {
        if (snapshot.exists()) {
            super.put((K) snapshot.getKey(), snapshot.getValue(getType()));
        }
    }

    @Override
    public void onChildChanged(DataSnapshot snapshot, String previousChildName) {
        super.put((K)snapshot.getKey(), snapshot.getValue(getType()));
    }

    @Override
    public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
        super.put((K)snapshot.getKey(), snapshot.getValue(getType()));
    }

    @Override
    public void onChildRemoved(DataSnapshot dataSnapshot) {
        super.remove(dataSnapshot.getKey());
    }

合同

需要 CRUD 接口才能不破坏 Map 契约。比如putreturns在给定位置插入新值时的前一个值,但是现在这个操作是异步.

对于写操作,这里的技巧是使用 CRUD for non blocking and Map 进行 阻塞 操作:

    @Override
    @WorkerThread
    public V put(K key, V value) {
        try {
            return Tasks.await(create(key, value));
        } catch (ExecutionException e) {
            return null;
        } catch (InterruptedException e) {
            return null;
        }
    }

数据绑定

免费,现在您还有 Android 数据绑定 用于您的 Map:

@Override
protected onCreate() {
    mUserMap = new UserArrayMap();
    mChangedCallback = new OnUserMapChanged();
}

@Override
protected void onResume() {
    super.onResume();
    mUserMap.addOnMapChangedCallback(mChangedCallback);
}

@Override
protected void onPause() {
    mUserMap.removeOnMapChangedCallback(mChangedCallback);
    super.onPause();
}

static class OnUserMapChanged extends OnMapChangedCallback<FirebaseArrayMap<String, User>, String, User> {

    @Override
    public void onMapChanged(FirebaseArrayMap<String, User> sender, String key) {
        Log.e(TAG, key);
        Log.e(TAG, sender.get(key).toString());
    }

}

记得在 onResume and onPause in order to avoid memory leaking caused by ChildEventListener 更新中(取消)注册您的回调。