Проблемы с соединениями Firestore, выполняющими потоки - PullRequest
0 голосов
/ 28 августа 2018

Прежде всего, я хотел бы извиниться, если название вводит в заблуждение. Английский не мой родной язык, и я не знал, как назвать этот пост. Теперь вопрос:

У меня есть Activity, которая показывает данные о пользователе, хранящиеся в проекте Firebase. Данные распределяются между пользователем Firebase (отображаемое имя, электронная почта и изображение профиля) и документом в Cloud Firestore с именем пользователя UID.

Когда начинается это действие, я запускаю Firebase google auth, чтобы получить пользователя, и затем возникают проблемы. Мне нужно знать, есть ли у пользователя связанный документ в базе данных с его дополнительными данными (существующий пользователь) или ему нужно создать его (новый пользователь). Я создал метод, который проверяет, существует ли документ с именем, подобным UID пользователя. Это метод:

public void userExists(String uid) {
    FirebaseFirestore db = FirebaseFirestore.getInstance();
    DocumentReference docRef = db.collection("users").document(uid);
    docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
        @Override
        public void onComplete(@NonNull Task<DocumentSnapshot> task) {
            if (task.isSuccessful()) {
                DocumentSnapshot documentSnapshot = task.getResult();
                if (documentSnapshot.exists()) {
                    aa = true;
                    aa=true;
                } else {
                    aa = false;
                }
            } else {
                aa = false;
            }
        }
    });
}

(aa - логическая переменная, объявленная в Activity).

Я вызываю этот метод внутри следующего, чтобы узнать, нужно ли мне начинать новое действие для создания документа или я могу без проблем отображать данные пользователя в текущем действии.

private void updateUI(FirebaseUser user) {

    if (user != null) {
        userExists(user.getUid());
        if(aa){
            //Fill layout with the user data and the user linked document data

            //USER DATA
            txvNombre=findViewById(R.id.nombrePerfil);
            txvNombre.setText(user.getDisplayName());
            imvAvatar=findViewById(R.id.imvVistaPerfilAvatar);
            Picasso.with(VistaPerfilActivity.this)
                    .load(user.getPhotoUrl())
                    .resize(500,500)
                    .centerCrop()
                    .into(imvAvatar);


            //HERE GOES THE DOCUMENT DATA


        }else{

        }
    } else {
        finish();
    }
}

Насколько я знаю, соединения Firestore устанавливаются в новом потоке, поэтому при запуске UpdateUI (пользователь FirebaseUser) aa всегда ложно, поскольку userExists (String uid) еще не завершен. userExists (String uid) работает правильно, я проверил.

Так что мне нужно знать, как проверить, завершен ли поток соединения Firestore, чтобы продолжить выполнение приложения. Я пытался использовать OnCompleteListener (показано в коде), но это не работает. Я также попытался просто написать действия в методе userExists (String uid) вместо простого изменения значения aa, а затем перейти к другому методу, но я получил

переменная, доступ к которой осуществляется из внутреннего класса, должна быть объявлена ​​окончательной

ошибка. Я пытался следовать совету Android Studio, чтобы сделать переменную окончательной, но я не могу работать с этим по понятным причинам.

Заранее спасибо.

Ответы [ 3 ]

0 голосов
/ 28 августа 2018

Вы правы, предполагая, что при использовании значения вашей переменной aa вне метода onComplete() данные еще не закончили загрузку из базы данных, и поэтому они недоступны. Таким образом, эта переменная всегда будет содержать начальное значение false.

Итак, чтобы решить эту проблему, нужно дождаться этого. Это можно сделать двумя способами. Первое решение - это очень быстрое решение, которое подразумевает, что вы используете значение вашей переменной aa только внутри метода onComplete(), и оно будет работать отлично. Во-вторых, если вы хотите использовать его вне метода onComplete(), я рекомендую вам увидеть последнюю часть моего ответа из этого поста , в котором я объяснил как это можно сделать с помощью пользовательского обратного вызова. Вы также можете взглянуть на это видео для лучшего понимания.

0 голосов
/ 28 августа 2018

Проблема не столько в многопоточности, сколько в том, что данные загружаются из Firebase асинхронно. К тому времени, когда ваша функция updateUI проверяет значение aa, onComplete еще не запускается.

Это легче всего увидеть, разместив несколько удачно расположенных операторов логирования:

System.out.println("Before attaching listener");
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DocumentSnapshot> task) {
        System.out.println("Got document");
    }
});
System.out.println("After attaching listener");

Когда вы запускаете этот код, он печатает

Перед подключением слушателя

После подключения слушателя

Получил документ

Вероятно, это не то, что вы ожидали, но оно точно объясняет, почему aa остается неизменным, когда updateUI проверяет его. Документ еще не был прочитан из Firestore, поэтому onComplete еще не запущен.

Решением для этого является перемещение всего кода, требующего данных из базы данных , в метод onComplete. Самый простой способ в вашем случае:

public void userExists(String uid) {
  FirebaseFirestore db = FirebaseFirestore.getInstance();
  DocumentReference docRef = db.collection("users").document(uid);
  docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DocumentSnapshot> task) {
        if (task.isSuccessful()) {
            DocumentSnapshot documentSnapshot = task.getResult();
            if (documentSnapshot.exists()) {
                //Fill layout with the user data and the user linked document data

                //USER DATA
                txvNombre=findViewById(R.id.nombrePerfil);
                txvNombre.setText(user.getDisplayName());
                imvAvatar=findViewById(R.id.imvVistaPerfilAvatar);
                Picasso.with(VistaPerfilActivity.this)
                        .load(user.getPhotoUrl())
                        .resize(500,500)
                        .centerCrop()
                        .into(imvAvatar);


                //HERE GOES THE DOCUMENT DATA

            }
        }
    }
  });
}

Теперь ваш код, который нуждается в документе, запускается только после того, как документ действительно доступен. Это будет работать, но делает функцию userExists несколько менее пригодной для повторного использования. Если вы хотите это исправить, вы можете передать обратный вызов в userExists, который вы затем вызовете после загрузки документа.

public interface UserExistsCallback {
  void onCallback(boolean isExisting);
}

И используйте это в userExists как:

public void userExists(String uid, final UserExistsCallback callback) {
  FirebaseFirestore db = FirebaseFirestore.getInstance();
  DocumentReference docRef = db.collection("users").document(uid);
  docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DocumentSnapshot> task) {
        boolean userExists = false;
        if (task.isSuccessful()) {
            DocumentSnapshot documentSnapshot = task.getResult();
            userExists = documentSnapshot.exists();
        }
        callback.onCallback(userExists);
    }
  });
}

И затем вызвать это из updateUI с помощью:

if (user != null) {
  userExists(user.getUid(), new UserExistsCallback() {
    public void onCallback(boolean isExisting) {
      if(isExisting){
        //Fill layout with the user data and the user linked document data

        //USER DATA
        txvNombre=findViewById(R.id.nombrePerfil);
        txvNombre.setText(user.getDisplayName());
        imvAvatar=findViewById(R.id.imvVistaPerfilAvatar);
        Picasso.with(VistaPerfilActivity.this)
                .load(user.getPhotoUrl())
                .resize(500,500)
                .centerCrop()
                .into(imvAvatar);


        //HERE GOES THE DOCUMENT DATA


      }else{

      }
    } else {
      finish();
    }
  });
}

Как вы можете видеть, наш `` обратный вызов очень похож на OnCompleteListener самого Firestore, но он немного более приспособлен к нашим потребностям.

Эта проблема часто появляется, поэтому я рекомендую потратить некоторое время на изучение этой проблемы. См:

0 голосов
/ 28 августа 2018

Что касается окончательной ссылки, если переменная принадлежит классу, а не объявлена ​​в методе, ее не нужно объявлять как окончательную.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...