Firebase如何同步共享数据?

4
我使用Firebase来处理我的Android应用程序的身份验证主题。我还在Firebase上保存用户配置文件,其中包含用户ID和用户可以在Android应用程序中更新的其他选项。
在启动时,应用程序会检查身份验证并重新加载用户配置文件(在Firebase上),然后更新应用程序中的userInstance。
Firebase被设置为应用程序级别的离线可用性。userInstance POJO与Firebase同步记录并在注销时停止同步。
我有一个特殊的参数,在我的用户配置文件中,我可以更改(作为管理员)。
每次我在Firebase控制台上更新它时,当应用程序再次启动时,它将被之前的值替换。
这是如何发生的?
另外: 1/ 如果多个客户端具有不同的本地值,则基于哪种机制合并数据?
这是一个更简单的代码示例,我试图重现错误。:
MyApplication.java
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Firebase.setAndroidContext(this);
        Firebase.getDefaultConfig().setLogLevel(Logger.Level.DEBUG);
        Firebase.getDefaultConfig().setPersistenceEnabled(true);


    }
}

MainActivity

public class MainActivity extends AppCompatActivity {

    Firebase ref;
    User user;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ref = new Firebase("https://millezim-test.firebaseIO.com").child("user");
        ref.keepSynced(true);

        Button br = (Button) findViewById(R.id.b_read);
        Button bs = (Button) findViewById(R.id.b_save);
        final TextView tv_r = (TextView) findViewById(R.id.tv_value_toread);
        final EditText tv_s = (EditText) findViewById(R.id.tv_value_tosave);

        user = new User();

        bs.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!tv_s.getText().toString().equalsIgnoreCase(""))
                    user.setAge(Integer.valueOf(tv_s.getText().toString()));
                ref.setValue(user);
            }
        });

        br.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ref.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        User u = dataSnapshot.getValue(User.class);
                        if (u != null)
                            tv_r.setText(String.valueOf(u.getAge()));
                        else
                            tv_r.setText("Bad Value");
                    }

                    @Override
                    public void onCancelled(FirebaseError firebaseError) {

                    }
                });
            }
        });

        ref.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                User u = dataSnapshot.getValue(User.class);
                u.setCounter(u.getCounter() + 1);
                user = u;
                saveUser();
            }

            @Override
            public void onCancelled(FirebaseError firebaseError) {

            }
        });
    }

    public void saveUser() {
        ref.setValue(user);
    }
}

如果您只是更改应用中的某个值,那么计数器会一直增加,直到应用停止:这很正常。但是奇怪的是,年龄会循环地从旧的变成新的,然后再变成旧的,而且不会停止。

enter image description here

我在我的应用程序中看到了类似的行为(没有循环),因为我没有计数器,但我无法在管理客户端中更改参数,我总是得到存储在移动设备上的先前值。

我只需要进行身份验证,然后使用AuthData +从Firebase获取的用户更新我的UserInstance(可能是缓存数据),然后将更新后的用户保存回Firebase(因为我可能会得到新的AuthData,并且我通常会从Firebase获取最新的用户)

2 / 在这个简单的示例中,我发现如果在开始时读取该值,则会获取应用程序中缓存的数据。如何强制使用在线数据?


不要仅仅描述你的代码问题,而是在问题中包含最小化的可复现代码。请参考http://stackoverflow.com/help/mcve - Frank van Puffelen
在我看来,系统问题和概念问题并不总是更好地通过代码来解释。顺便说一下,我创建了一个简单的应用程序,触发了一个问题,似乎与我的问题有关。 - Anthony
3个回答

10

问题在于你正在使用磁盘持久化和单值事件监听器。当你执行以下操作时:

ref.addListenerForSingleValueEvent(new ValueEventListener() {...

您正在请求该位置(在您的情况下为用户)的单个值。如果Firebase在其本地缓存中具有该位置的值,则会立即针对该值触发。

解决此问题的最佳方法是不使用单个值侦听器,而是使用常规事件侦听器。这样,您将获得两个事件:一个用于缓存版本,另一个用于从服务器返回的版本(如果它不同)。

唯一的替代方法是不使用Firebase的磁盘持久性。如果没有这个,数据就没有本地缓存供重新启动时读取。

有关此组合的讨论有几个在Firebase邮件列表上进行。以下是其中之一:https://groups.google.com/forum/#!msg/firebase-talk/ptTtEyBDKls/XbNKD_K8CQAJ


好的,谢谢你提供的链接,它帮助我理解了。所以我明白了你的建议来解决这个问题,我会尝试一下。但是我不确定为什么它在旧值和新值之间无限切换!请看我的理解场景。 - Anthony
这是怎么发生的?
  1. GetValue:旧值(singleValueListener)
  2. SaveValue:旧值
  3. GetValue:新值(因为它是keepsync(true))
  4. SaveValue:新值
如果2在11完成之前注册,那么就会出现循环!
- Anthony
将Single监听器更改为Normal监听器并不能解决问题。 - Anthony
设置 Firebase.getDefaultConfig().setPersistenceEnabled(false); 并没有解决问题! - Anthony
1
这是否意味着我可以始终期望定期事件监听器触发两次,无论缓存同步状态如何?第二次它将始终是来自服务器的最新值? - Sreekanth
显示剩余4条评论

1
在仔细考虑后,我的当前策略是使用Firebase进行数据持久化,不再使用自己的对象。(之前我需要同步UI、我的数据、Firebase缓存和服务器数据)
所以现在,我使用:
  • 使用磁盘缓存
  • 使用onValueEventListener
    • 保持更新数据(只有组件需要同步数据时才读取)
    • 触发更新UI事件(适用于可以接受异步数据的组件)
  • 定义一些特定的setter,用于在Firebase上更新数据(不再在应用程序层面上进行)
这意味着,当我更新数据时,它会被发送到服务器(或Firebase缓存层),直到返回到UI。由于Firebase处理此缓存,所以如果速度足够快,则足够快,并且Firebase处理网络同步。

0
将@frank-van-puffelen的(1)解决方案转化为代码:
public class MainActivity extends AppCompatActivity {

    Firebase ref;
    User user;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ref = new Firebase("https://test.firebaseIO.com").child("user");
        ref.keepSynced(true);

        Button br = (Button) findViewById(R.id.b_read);
        Button bs = (Button) findViewById(R.id.b_save);
        final TextView tv_r = (TextView) findViewById(R.id.tv_value_toread);
        final EditText tv_s = (EditText) findViewById(R.id.tv_value_tosave);

        user = new User();

        bs.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!tv_s.getText().toString().equalsIgnoreCase(""))
                    user.setAge(Integer.valueOf(tv_s.getText().toString()));
                ref.setValue(user);
            }
        });

        br.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ref.addValueEventListener(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot dataSnapshot) {
                        User u = dataSnapshot.getValue(User.class);
                        if (u != null)
                            tv_r.setText(String.valueOf(u.getAge()));
                        else
                            tv_r.setText("Bad Value");
                    }

                    @Override
                    public void onCancelled(FirebaseError firebaseError) {

                    }
                });
            }
        });

        ref.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                User u = dataSnapshot.getValue(User.class);
                u.setCounter(u.getCounter() + 1);
                user = u;
                saveUser();
            }

            @Override
            public void onCancelled(FirebaseError firebaseError) {

            }
        });
    }

    public void saveUser() {
        ref.setValue(user);
    }
}

然而,这并没有改变什么,甚至更糟。现在似乎每个设置的值都会留下一个幽灵值(由客户端/服务器请求保持),并且可以看到每个值的切换!

编辑

以下代码可行!拥有一个正常的ValueListener,在再次保存值之前停止它,并在完成保存后重新启用它!(好吧,我认为这可能是在Firebase框架中完成的)。

public class MainActivity extends AppCompatActivity {

    Firebase ref;
    User user;
    private ValueEventListener theListener;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ref = new Firebase("https://test.firebaseIO.com").child("user");
        ref.keepSynced(false);

        Button bs = (Button) findViewById(R.id.b_save);
        final EditText tv_s = (EditText) findViewById(R.id.tv_value_tosave);

        user = new User();

        bs.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!tv_s.getText().toString().equalsIgnoreCase(""))
                    user.setAge(Integer.valueOf(tv_s.getText().toString()));
                ref.setValue(user);
            }
        });


        theListener = new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                User u = dataSnapshot.getValue(User.class);
                u.setCounter(u.getCounter() + 1);
                user = u;
                updateUI(u);
                saveUser();
            }

            @Override
            public void onCancelled(FirebaseError firebaseError) {

            }
        };

        ref.addValueEventListener(theListener);
    }

    public void saveUser() {
        ref.removeEventListener(theListener);
        ref.setValue(user, new Firebase.CompletionListener() {
            @Override
            public void onComplete(FirebaseError firebaseError, Firebase firebase) {
                ref.addValueEventListener(theListener);
            }
        });
    }

    public void updateUI(User user) {

        TextView tv_r = (TextView) findViewById(R.id.tv_value_toread);
        if (user != null)
            tv_r.setText(String.valueOf(user.getAge()));
        else
            tv_r.setText("Bad Value");
    }
}

编辑

然而,这并不允许在管理页面上更改值。年龄值被设置,然后回到在移动设备上保存的值。

因此,我想唯一的解决方案是在系统级别解决。请勿为应用程序可以保存并且可以由第三方应用程序保存的值使用VALUELISTENER。请指导/纠正此假设!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接