Эта страница была переведа с помощью Cloud Translation API.
Switch to English

Чтение и запись данных на Android

В этом документе описаны основы чтения и записи данных Firebase.

Данные Firebase записываются в ссылку FirebaseDatabase и извлекаются путем присоединения к ссылке асинхронного прослушивателя. Слушатель запускается один раз для начального состояния данных и снова при каждом изменении данных.

Получить ссылку на базу данных

Для чтения или записи данных из базы данных вам понадобится экземпляр DatabaseReference :

Ява

private DatabaseReference mDatabase;
// ...
mDatabase = FirebaseDatabase.getInstance().getReference();

Котлин + KTX

private lateinit var database: DatabaseReference
// ...
database = Firebase.database.reference

Чтение и запись данных

Основные операции записи

Для основных операций записи вы можете использовать setValue() для сохранения данных по указанной ссылке, заменяя любые существующие данные по этому пути. Вы можете использовать этот метод для:

  • Типы передачи, соответствующие доступным типам JSON, следующие:
    • String
    • Long
    • Double
    • Boolean
    • Map<String, Object>
    • List<Object>
  • Передайте пользовательский объект Java, если класс, который его определяет, имеет конструктор по умолчанию, который не принимает аргументов и имеет общедоступные геттеры для присваиваемых свойств.

Если вы используете объект Java, содержимое вашего объекта автоматически сопоставляется с дочерними местоположениями во вложенном режиме. Использование объекта Java также обычно делает ваш код более читабельным и легким в обслуживании. Например, если у вас есть приложение с базовым профилем пользователя, ваш объект User может выглядеть следующим образом:

Ява

@IgnoreExtraProperties
public class User {

    public String username;
    public String email;

    public User() {
        // Default constructor required for calls to DataSnapshot.getValue(User.class)
    }

    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

}

Котлин + KTX

@IgnoreExtraProperties
data class User(
    var username: String? = "",
    var email: String? = ""
)

Вы можете добавить пользователя с помощью setValue() следующим образом:

Ява

private void writeNewUser(String userId, String name, String email) {
    User user = new User(name, email);

    mDatabase.child("users").child(userId).setValue(user);
}

Котлин + KTX

private fun writeNewUser(userId: String, name: String, email: String?) {
    val user = User(name, email)
    database.child("users").child(userId).setValue(user)
}

Использование setValue() таким образом перезаписывает данные в указанном месте, включая любые дочерние узлы. Однако вы все равно можете обновить дочерний объект, не перезаписывая весь объект. Если вы хотите разрешить пользователям обновлять свои профили, вы можете обновить имя пользователя следующим образом:

Ява

mDatabase.child("users").child(userId).child("username").setValue(name);

Котлин + KTX

database.child("users").child(userId).child("username").setValue(name)

Слушайте важные события

Чтобы читать данные по пути и прослушивать изменения, используйте метод addValueEventListener() или addListenerForSingleValueEvent() чтобы добавить ValueEventListener в DatabaseReference .

слушатель Обратный вызов события Типичное использование
ValueEventListener onDataChange() Читайте и слушайте изменения всего содержимого пути.

Вы можете использовать метод onDataChange() для чтения статического снимка содержимого по заданному пути в том виде, в каком они существовали на момент события. Этот метод запускается один раз при подключении слушателя и снова каждый раз, когда данные, включая дочерние, изменяются. Обратному вызову события передается моментальный снимок, содержащий все данные в этом месте, включая дочерние данные. Если данных нет, моментальный снимок вернет false при вызове exists() и null при вызове getValue() для него.

В следующем примере демонстрируется приложение для ведения социального блога, которое извлекает сведения о публикации из базы данных:

Ява

ValueEventListener postListener = new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // Get Post object and use the values to update the UI
        Post post = dataSnapshot.getValue(Post.class);
        // ...
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
        // ...
    }
};
mPostReference.addValueEventListener(postListener);

Котлин + KTX

val postListener = object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        // Get Post object and use the values to update the UI
        val post = dataSnapshot.getValue<Post>()
        // ...
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
        // ...
    }
}
postReference.addValueEventListener(postListener)

Слушатель получает DataSnapshot который содержит данные в указанном месте в базе данных во время события. Вызов getValue() на снимке состояния возвращает объектное представление данных Java. Если в этом месте нет данных, вызов getValue() возвращает null .

В этом примере ValueEventListener также определяет метод onCancelled() который вызывается, если чтение отменяется. Например, чтение можно отменить, если у клиента нет разрешения на чтение из расположения базы данных Firebase. Этому методу передается объект DatabaseError указывающий, почему произошел сбой.

Прочитать данные один раз

В некоторых случаях вы можете захотеть, чтобы обратный вызов вызывался один раз, а затем сразу же удалялся, например, при инициализации элемента пользовательского интерфейса, который вы не ожидаете изменить. Вы можете использовать метод addListenerForSingleValueEvent() чтобы упростить этот сценарий: он срабатывает один раз, а затем больше не запускается.

Это полезно для данных, которые необходимо загрузить только один раз, и не предполагается, что они будут часто меняться или требовать активного прослушивания. Например, приложение для ведения блога в предыдущих примерах использует этот метод для загрузки профиля пользователя, когда он начинает создание нового сообщения:

Обновление или удаление данных

Обновить определенные поля

Чтобы одновременно писать в определенные дочерние узлы, не перезаписывая другие дочерние узлы, используйте метод updateChildren() .

При вызове updateChildren() вы можете обновить дочерние значения нижнего уровня, указав путь для ключа. Если данные хранятся в нескольких местах для лучшего масштабирования, вы можете обновить все экземпляры этих данных, используя разветвление данных . Например, приложение для ведения социального блога может иметь такой класс Post :

Ява

@IgnoreExtraProperties
public class Post {

    public String uid;
    public String author;
    public String title;
    public String body;
    public int starCount = 0;
    public Map<String, Boolean> stars = new HashMap<>();

    public Post() {
        // Default constructor required for calls to DataSnapshot.getValue(Post.class)
    }

    public Post(String uid, String author, String title, String body) {
        this.uid = uid;
        this.author = author;
        this.title = title;
        this.body = body;
    }

    @Exclude
    public Map<String, Object> toMap() {
        HashMap<String, Object> result = new HashMap<>();
        result.put("uid", uid);
        result.put("author", author);
        result.put("title", title);
        result.put("body", body);
        result.put("starCount", starCount);
        result.put("stars", stars);

        return result;
    }

}

Котлин + KTX

@IgnoreExtraProperties
data class Post(
    var uid: String? = "",
    var author: String? = "",
    var title: String? = "",
    var body: String? = "",
    var starCount: Int = 0,
    var stars: MutableMap<String, Boolean> = HashMap()
) {

    @Exclude
    fun toMap(): Map<String, Any?> {
        return mapOf(
                "uid" to uid,
                "author" to author,
                "title" to title,
                "body" to body,
                "starCount" to starCount,
                "stars" to stars
        )
    }
}

Чтобы создать сообщение и одновременно обновить его до канала последних действий и канала активности пользователя, публикующего сообщения, приложение для ведения блога использует такой код:

Ява

private void writeNewPost(String userId, String username, String title, String body) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    String key = mDatabase.child("posts").push().getKey();
    Post post = new Post(userId, username, title, body);
    Map<String, Object> postValues = post.toMap();

    Map<String, Object> childUpdates = new HashMap<>();
    childUpdates.put("/posts/" + key, postValues);
    childUpdates.put("/user-posts/" + userId + "/" + key, postValues);

    mDatabase.updateChildren(childUpdates);
}

Котлин + KTX

private fun writeNewPost(userId: String, username: String, title: String, body: String) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    val key = database.child("posts").push().key
    if (key == null) {
        Log.w(TAG, "Couldn't get push key for posts")
        return
    }

    val post = Post(userId, username, title, body)
    val postValues = post.toMap()

    val childUpdates = hashMapOf<String, Any>(
            "/posts/$key" to postValues,
            "/user-posts/$userId/$key" to postValues
    )

    database.updateChildren(childUpdates)
}

В этом примере push() для создания сообщения в узле, содержащего сообщения для всех пользователей в /posts/$postid и одновременного получения ключа с помощью getKey() . Затем ключ можно использовать для создания второй записи в сообщениях /user-posts/$userid/$postid в /user-posts/$userid/$postid .

Используя эти пути, вы можете выполнять одновременные обновления в нескольких местах в дереве JSON с помощью одного вызова updateChildren() , например, как в этом примере создается новый пост в обоих местах. Одновременные обновления, сделанные таким образом, являются атомарными: либо все обновления выполняются успешно, либо все обновления не выполняются.

Добавить обратный вызов о завершении

Если вы хотите знать, когда ваши данные были зафиксированы, вы можете добавить прослушиватель завершения. Оба setValue() и updateChildren() принимают необязательный прослушиватель завершения, который вызывается, когда запись была успешно зафиксирована в базе данных. Если вызов был неудачным, слушателю передается объект ошибки, указывающий, почему произошла ошибка.

Ява

mDatabase.child("users").child(userId).setValue(user)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                // Write was successful!
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Write failed
                // ...
            }
        });

Котлин + KTX

database.child("users").child(userId).setValue(user)
        .addOnSuccessListener {
            // Write was successful!
            // ...
        }
        .addOnFailureListener {
            // Write failed
            // ...
        }

Удалить данные

Самый простой способ удалить данные - вызвать removeValue() для ссылки на расположение этих данных.

Вы также можете удалить, указав null в качестве значения для другой операции записи, такой как setValue() или updateChildren() . Вы можете использовать эту технику с updateChildren() для удаления нескольких дочерних updateChildren() за один вызов API.

Отключить слушателей

removeEventListener() удаляются путем вызова removeEventListener() в removeEventListener() на базу данных Firebase.

Если прослушиватель был добавлен несколько раз к местоположению данных, он вызывается несколько раз для каждого события, и вы должны отсоединять его столько же раз, чтобы удалить его полностью.

Вызов removeEventListener() для родительского слушателя не удаляет автоматически слушателей, зарегистрированных на его дочерних узлах; removeEventListener() также должен вызываться для всех дочерних слушателей, чтобы удалить обратный вызов.

Сохранять данные как транзакции

При работе с данными, которые могут быть повреждены параллельными модификациями, такими как добавочные счетчики, вы можете использовать операцию транзакции . Вы даете этой операции два аргумента: функцию обновления и необязательный обратный вызов завершения. Функция обновления принимает текущее состояние данных в качестве аргумента и возвращает новое желаемое состояние, которое вы хотите записать. Если другой клиент записывает в это место до того, как ваше новое значение будет успешно записано, ваша функция обновления вызывается снова с новым текущим значением, и запись повторяется.

Например, в примере приложения для ведения социального блога вы можете разрешить пользователям отмечать и снимать пометки с сообщений и отслеживать, сколько звезд получил сообщение, следующим образом:

Ява

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(new Transaction.Handler() {
        @Override
        public Transaction.Result doTransaction(MutableData mutableData) {
            Post p = mutableData.getValue(Post.class);
            if (p == null) {
                return Transaction.success(mutableData);
            }

            if (p.stars.containsKey(getUid())) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1;
                p.stars.remove(getUid());
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1;
                p.stars.put(getUid(), true);
            }

            // Set value and report transaction success
            mutableData.setValue(p);
            return Transaction.success(mutableData);
        }

        @Override
        public void onComplete(DatabaseError databaseError, boolean committed,
                               DataSnapshot currentData) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError);
        }
    });
}

Котлин + KTX

private fun onStarClicked(postRef: DatabaseReference) {
    postRef.runTransaction(object : Transaction.Handler {
        override fun doTransaction(mutableData: MutableData): Transaction.Result {
            val p = mutableData.getValue(Post::class.java)
                    ?: return Transaction.success(mutableData)

            if (p.stars.containsKey(uid)) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1
                p.stars.remove(uid)
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1
                p.stars[uid] = true
            }

            // Set value and report transaction success
            mutableData.value = p
            return Transaction.success(mutableData)
        }

        override fun onComplete(
            databaseError: DatabaseError?,
            committed: Boolean,
            currentData: DataSnapshot?
        ) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError!!)
        }
    })
}

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

Запись данных в автономном режиме

Если клиент потеряет сетевое соединение, ваше приложение продолжит работать правильно.

Каждый клиент, подключенный к базе данных Firebase, поддерживает собственную внутреннюю версию любых активных данных. Когда данные записываются, они сначала записываются в эту локальную версию. Затем клиент Firebase синхронизирует эти данные с удаленными серверами баз данных и с другими клиентами по принципу «максимальных усилий».

В результате все операции записи в базу данных немедленно вызывают локальные события, прежде чем какие-либо данные будут записаны на сервер. Это означает, что ваше приложение остается отзывчивым независимо от задержки сети или подключения.

После восстановления подключения ваше приложение получает соответствующий набор событий, чтобы клиент синхронизировался с текущим состоянием сервера без необходимости писать какой-либо пользовательский код.

Следующие шаги