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

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

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

(Необязательно) Создайте прототип и протестируйте его с помощью Firebase Local Emulator Suite

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

Эмулятор Realtime Database является частью Local Emulator Suite , который позволяет вашему приложению взаимодействовать с содержимым и конфигурацией эмулируемой базы данных, а также, при необходимости, с ресурсами эмулируемого проекта (функциями, другими базами данных и правилами безопасности).

Использование эмулятора Realtime Database включает всего несколько шагов:

  1. Добавление строки кода в тестовую конфигурацию вашего приложения для подключения к эмулятору.
  2. Из корня локального каталога проекта запустите firebase emulators:start .
  3. Выполнение вызовов из кода прототипа вашего приложения с использованием SDK платформы Realtime Database , как обычно, или с помощью REST API Realtime Database .

Доступно подробное пошаговое руководство, включающее Realtime Database и Cloud Functions . Вам также следует ознакомиться с введением Local Emulator Suite .

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

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

Kotlin+KTX

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

Java

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

Запись данных

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

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

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

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

Kotlin+KTX

@IgnoreExtraProperties
data class User(val username: String? = null, val email: String? = null) {
    // Null default values create a no-argument default constructor, which is needed
    // for deserialization from a DataSnapshot.
}

Java

@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;
    }

}

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

Kotlin+KTX

fun writeNewUser(userId: String, name: String, email: String) {
    val user = User(name, email)

    database.child("users").child(userId).setValue(user)
}

Java

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

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

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

Kotlin+KTX

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

Java

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

Чтение данных

Чтение данных с постоянными прослушивателями

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

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

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

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

Kotlin+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)

Java

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);

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

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

Считайте данные один раз

Прочитайте один раз, используя get()

SDK предназначен для управления взаимодействием с серверами баз данных независимо от того, находится ли ваше приложение в сети или офлайн.

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

Если вам нужны данные только один раз, вы можете использовать get() чтобы получить снимок данных из базы данных. Если по какой-либо причине get() не может вернуть значение сервера, клиент проверит кэш локального хранилища и вернет ошибку, если значение все еще не найдено.

Ненужное использование get() может увеличить использование полосы пропускания и привести к потере производительности, которую можно предотвратить, используя прослушиватель реального времени, как показано выше.

Kotlin+KTX

mDatabase.child("users").child(userId).get().addOnSuccessListener {
    Log.i("firebase", "Got value ${it.value}")
}.addOnFailureListener{
    Log.e("firebase", "Error getting data", it)
}

Java

mDatabase.child("users").child(userId).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DataSnapshot> task) {
        if (!task.isSuccessful()) {
            Log.e("firebase", "Error getting data", task.getException());
        }
        else {
            Log.d("firebase", String.valueOf(task.getResult().getValue()));
        }
    }
});

Прочитайте один раз, используя прослушиватель

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

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

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

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

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

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

Kotlin+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,
        )
    }
}

Java

@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;
    }
}

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

Kotlin+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)
}

Java

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);
}

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

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

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

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

Kotlin+KTX

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

Java

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
                // ...
            }
        });

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

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

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

Отключить прослушиватели

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

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

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

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

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

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

Kotlin+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!!)
        }
    })
}

Java

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(new Transaction.Handler() {
        @NonNull
        @Override
        public Transaction.Result doTransaction(@NonNull 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);
        }
    });
}

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

Атомарные приращения на стороне сервера

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

Kotlin+KTX

private fun onStarClicked(uid: String, key: String) {
    val updates: MutableMap<String, Any> = hashMapOf(
        "posts/$key/stars/$uid" to true,
        "posts/$key/starCount" to ServerValue.increment(1),
        "user-posts/$uid/$key/stars/$uid" to true,
        "user-posts/$uid/$key/starCount" to ServerValue.increment(1),
    )
    database.updateChildren(updates)
}

Java

private void onStarClicked(String uid, String key) {
    Map<String, Object> updates = new HashMap<>();
    updates.put("posts/"+key+"/stars/"+uid, true);
    updates.put("posts/"+key+"/starCount", ServerValue.increment(1));
    updates.put("user-posts/"+uid+"/"+key+"/stars/"+uid, true);
    updates.put("user-posts/"+uid+"/"+key+"/starCount", ServerValue.increment(1));
    mDatabase.updateChildren(updates);
}

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

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

Работайте с данными оффлайн

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

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

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

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

Мы поговорим подробнее о поведении в автономном режиме в разделе Узнайте больше о возможностях онлайн и офлайн .

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

,

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

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

(Необязательно) Создайте прототип и протестируйте его с помощью Firebase Local Emulator Suite

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

Эмулятор Realtime Database является частью Local Emulator Suite , который позволяет вашему приложению взаимодействовать с содержимым и конфигурацией эмулируемой базы данных, а также, при необходимости, с ресурсами эмулируемого проекта (функциями, другими базами данных и правилами безопасности).

Использование эмулятора Realtime Database включает всего несколько шагов:

  1. Добавление строки кода в тестовую конфигурацию вашего приложения для подключения к эмулятору.
  2. Из корня локального каталога проекта запустите firebase emulators:start .
  3. Выполнение вызовов из кода прототипа вашего приложения с использованием SDK платформы Realtime Database , как обычно, или с помощью REST API Realtime Database .

Доступно подробное пошаговое руководство, включающее Realtime Database и Cloud Functions . Вам также следует ознакомиться с введением Local Emulator Suite .

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

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

Kotlin+KTX

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

Java

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

Запись данных

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

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

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

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

Kotlin+KTX

@IgnoreExtraProperties
data class User(val username: String? = null, val email: String? = null) {
    // Null default values create a no-argument default constructor, which is needed
    // for deserialization from a DataSnapshot.
}

Java

@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;
    }

}

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

Kotlin+KTX

fun writeNewUser(userId: String, name: String, email: String) {
    val user = User(name, email)

    database.child("users").child(userId).setValue(user)
}

Java

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

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

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

Kotlin+KTX

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

Java

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

Чтение данных

Чтение данных с постоянными прослушивателями

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

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

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

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

Kotlin+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)

Java

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);

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

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

Считайте данные один раз

Прочитайте один раз, используя get()

SDK предназначен для управления взаимодействием с серверами баз данных независимо от того, находится ли ваше приложение в сети или офлайн.

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

Если вам нужны данные только один раз, вы можете использовать get() чтобы получить снимок данных из базы данных. Если по какой-либо причине get() не может вернуть значение сервера, клиент проверит кэш локального хранилища и вернет ошибку, если значение все еще не найдено.

Ненужное использование get() может увеличить использование полосы пропускания и привести к потере производительности, которую можно предотвратить, используя прослушиватель реального времени, как показано выше.

Kotlin+KTX

mDatabase.child("users").child(userId).get().addOnSuccessListener {
    Log.i("firebase", "Got value ${it.value}")
}.addOnFailureListener{
    Log.e("firebase", "Error getting data", it)
}

Java

mDatabase.child("users").child(userId).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DataSnapshot> task) {
        if (!task.isSuccessful()) {
            Log.e("firebase", "Error getting data", task.getException());
        }
        else {
            Log.d("firebase", String.valueOf(task.getResult().getValue()));
        }
    }
});

Прочитайте один раз, используя прослушиватель

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

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

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

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

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

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

Kotlin+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,
        )
    }
}

Java

@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;
    }
}

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

Kotlin+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)
}

Java

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);
}

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

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

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

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

Kotlin+KTX

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

Java

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
                // ...
            }
        });

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

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

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

Отключить прослушиватели

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

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

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

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

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

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

Kotlin+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!!)
        }
    })
}

Java

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(new Transaction.Handler() {
        @NonNull
        @Override
        public Transaction.Result doTransaction(@NonNull 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);
        }
    });
}

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

Атомарные приращения на стороне сервера

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

Kotlin+KTX

private fun onStarClicked(uid: String, key: String) {
    val updates: MutableMap<String, Any> = hashMapOf(
        "posts/$key/stars/$uid" to true,
        "posts/$key/starCount" to ServerValue.increment(1),
        "user-posts/$uid/$key/stars/$uid" to true,
        "user-posts/$uid/$key/starCount" to ServerValue.increment(1),
    )
    database.updateChildren(updates)
}

Java

private void onStarClicked(String uid, String key) {
    Map<String, Object> updates = new HashMap<>();
    updates.put("posts/"+key+"/stars/"+uid, true);
    updates.put("posts/"+key+"/starCount", ServerValue.increment(1));
    updates.put("user-posts/"+uid+"/"+key+"/stars/"+uid, true);
    updates.put("user-posts/"+uid+"/"+key+"/starCount", ServerValue.increment(1));
    mDatabase.updateChildren(updates);
}

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

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

Работайте с данными оффлайн

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

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

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

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

Мы поговорим подробнее о поведении в автономном режиме в разделе Узнайте больше о возможностях онлайн и офлайн .

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

,

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

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

(Необязательно) Создайте прототип и протестируйте его с помощью Firebase Local Emulator Suite

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

Эмулятор Realtime Database является частью Local Emulator Suite , который позволяет вашему приложению взаимодействовать с содержимым и конфигурацией эмулируемой базы данных, а также, при необходимости, с ресурсами эмулируемого проекта (функциями, другими базами данных и правилами безопасности).

Использование эмулятора Realtime Database включает всего несколько шагов:

  1. Добавление строки кода в тестовую конфигурацию вашего приложения для подключения к эмулятору.
  2. Из корня локального каталога проекта запустите firebase emulators:start .
  3. Выполнение вызовов из кода прототипа вашего приложения с использованием SDK платформы Realtime Database , как обычно, или с помощью REST API Realtime Database .

Доступно подробное пошаговое руководство, включающее Realtime Database и Cloud Functions . Вам также следует ознакомиться с введением Local Emulator Suite .

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

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

Kotlin+KTX

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

Java

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

Запись данных

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

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

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

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

Kotlin+KTX

@IgnoreExtraProperties
data class User(val username: String? = null, val email: String? = null) {
    // Null default values create a no-argument default constructor, which is needed
    // for deserialization from a DataSnapshot.
}

Java

@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;
    }

}

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

Kotlin+KTX

fun writeNewUser(userId: String, name: String, email: String) {
    val user = User(name, email)

    database.child("users").child(userId).setValue(user)
}

Java

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

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

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

Kotlin+KTX

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

Java

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

Чтение данных

Чтение данных с постоянными прослушивателями

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

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

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

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

Kotlin+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)

Java

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);

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

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

Считайте данные один раз

Прочитайте один раз, используя get()

SDK предназначен для управления взаимодействием с серверами баз данных независимо от того, находится ли ваше приложение в сети или офлайн.

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

Если вам нужны данные только один раз, вы можете использовать get() чтобы получить снимок данных из базы данных. Если по какой-либо причине get() не может вернуть значение сервера, клиент проверит кэш локального хранилища и вернет ошибку, если значение все еще не найдено.

Ненужное использование get() может увеличить использование полосы пропускания и привести к потере производительности, которую можно предотвратить, используя прослушиватель реального времени, как показано выше.

Kotlin+KTX

mDatabase.child("users").child(userId).get().addOnSuccessListener {
    Log.i("firebase", "Got value ${it.value}")
}.addOnFailureListener{
    Log.e("firebase", "Error getting data", it)
}

Java

mDatabase.child("users").child(userId).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DataSnapshot> task) {
        if (!task.isSuccessful()) {
            Log.e("firebase", "Error getting data", task.getException());
        }
        else {
            Log.d("firebase", String.valueOf(task.getResult().getValue()));
        }
    }
});

Прочитайте один раз, используя прослушиватель

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

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

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

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

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

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

Kotlin+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,
        )
    }
}

Java

@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;
    }
}

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

Kotlin+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)
}

Java

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);
}

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

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

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

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

Kotlin+KTX

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

Java

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
                // ...
            }
        });

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

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

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

Снимите слушателей

Обратные вызовы удаляются, вызывая метод removeEventListener() в вашей ссылке на базу данных FireBase.

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

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

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

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

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

Kotlin+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!!)
        }
    })
}

Java

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(new Transaction.Handler() {
        @NonNull
        @Override
        public Transaction.Result doTransaction(@NonNull 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);
        }
    });
}

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

Атомный сервер-шаг на стороне

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

Kotlin+KTX

private fun onStarClicked(uid: String, key: String) {
    val updates: MutableMap<String, Any> = hashMapOf(
        "posts/$key/stars/$uid" to true,
        "posts/$key/starCount" to ServerValue.increment(1),
        "user-posts/$uid/$key/stars/$uid" to true,
        "user-posts/$uid/$key/starCount" to ServerValue.increment(1),
    )
    database.updateChildren(updates)
}

Java

private void onStarClicked(String uid, String key) {
    Map<String, Object> updates = new HashMap<>();
    updates.put("posts/"+key+"/stars/"+uid, true);
    updates.put("posts/"+key+"/starCount", ServerValue.increment(1));
    updates.put("user-posts/"+uid+"/"+key+"/stars/"+uid, true);
    updates.put("user-posts/"+uid+"/"+key+"/starCount", ServerValue.increment(1));
    mDatabase.updateChildren(updates);
}

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

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

Работать с данными в автономном режиме

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

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

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

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

Мы поговорим больше о автономном поведении в узнать больше о онлайн -и автономных возможностях .

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

,

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

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

(Необязательно) Прототип и тестирование с помощью Firebase Local Emulator Suite

Прежде чем говорить о том, как ваше приложение читает и записывается в Realtime Database , давайте представим набор инструментов, которые вы можете использовать для прототипа и проверки функций Realtime Database : Firebase Local Emulator Suite . Если вы пробуете различные модели данных, оптимизируете свои правила безопасности или работаете над тем, чтобы найти наиболее экономически эффективный способ взаимодействия с бэк-эндом, возможность работать на местном уровне без развертывания живых услуг может быть отличной идеей.

Эмулятор Realtime Database является частью Local Emulator Suite , который позволяет вашему приложению взаимодействовать с вашим эмулированным содержанием базы данных и конфигурацией, а также, опционально ваши эмулированные ресурсы проекта (функции, другие базы данных и правила безопасности).

Использование эмулятора Realtime Database включает в себя всего несколько шагов:

  1. Добавление строки кода в тестовую конфигурацию вашего приложения для подключения к эмулятору.
  2. Из корня вашего местного каталога проекта управляет firebase emulators:start .
  3. Создание вызовов из кода прототипа вашего приложения с использованием платформы Realtime Database , как обычно, или с использованием API REST Realtime Database .

Доступно подробное прохождение с участием Realtime Database и Cloud Functions . Вы также должны взглянуть на введение Local Emulator Suite .

Получите базу данных

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

Kotlin+KTX

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

Java

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

Напишите данные

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

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

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

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

Kotlin+KTX

@IgnoreExtraProperties
data class User(val username: String? = null, val email: String? = null) {
    // Null default values create a no-argument default constructor, which is needed
    // for deserialization from a DataSnapshot.
}

Java

@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;
    }

}

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

Kotlin+KTX

fun writeNewUser(userId: String, name: String, email: String) {
    val user = User(name, email)

    database.child("users").child(userId).setValue(user)
}

Java

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

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

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

Kotlin+KTX

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

Java

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

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

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

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

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

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

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

Kotlin+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)

Java

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);

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

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

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

Читайте один раз, используя get ()

SDK предназначен для управления взаимодействиями с серверами базы данных, независимо от того, является ли ваше приложение онлайн или офлайн.

Как правило, вам следует использовать методы ValueEventListener описанные выше, чтобы прочитать данные, чтобы получить уведомление об обновлениях данных из бэкэнда. Техника слушателя уменьшает ваше использование и выставление счетов, и они оптимизированы, чтобы дать вашим пользователям лучший опыт, когда они выходят в Интернет и офлайн.

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

Ненужное использование get() может увеличить использование полосы пропускания и привести к потере производительности, что можно предотвратить, используя слушателя в реальном времени, как показано выше.

Kotlin+KTX

mDatabase.child("users").child(userId).get().addOnSuccessListener {
    Log.i("firebase", "Got value ${it.value}")
}.addOnFailureListener{
    Log.e("firebase", "Error getting data", it)
}

Java

mDatabase.child("users").child(userId).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DataSnapshot> task) {
        if (!task.isSuccessful()) {
            Log.e("firebase", "Error getting data", task.getException());
        }
        else {
            Log.d("firebase", String.valueOf(task.getResult().getValue()));
        }
    }
});

Читайте однажды, используя слушателя

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

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

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

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

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

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

Kotlin+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,
        )
    }
}

Java

@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",