Realtime Database

Real-time synchronization, presence detection, and offline support for your Blazor WebAssembly applications.

Overview

Firebase Realtime Database is a cloud-hosted NoSQL database that lets you store and sync data between your users in real-time. FireBlazor provides a type-safe, intuitive API for working with Realtime Database in your Blazor WebAssembly applications.

Key features include:

  • Real-time synchronization - Data is synced across all clients in milliseconds
  • Presence detection - Track user online/offline status
  • Offline support - Data is persisted locally and synced when connectivity is restored
  • Server values - Timestamps and atomic increments handled server-side
  • Transactions - Atomic read-modify-write operations
Offline Support

Firebase Realtime Database automatically caches data locally and handles reconnection seamlessly. Your app will continue to work offline, and changes will be synced when connectivity is restored. This is handled automatically - no additional configuration required.

Basic Operations

FireBlazor provides a clean, fluent API for all standard Realtime Database operations. All operations return a Result<T> type for functional error handling.

Set Data

Use SetAsync to write or replace data at a specific location. This will overwrite any existing data at that path.

// Set data at a specific path
await Firebase.RealtimeDb.Ref("users/user1").SetAsync(userData);

// Example with a user object
var user = new User
{
    Name = "John Doe",
    Email = "john@example.com",
    CreatedAt = DateTime.UtcNow
};
await Firebase.RealtimeDb.Ref("users/user123").SetAsync(user);

Push Data

Use PushAsync to add data with an auto-generated unique key. This is ideal for lists where you want Firebase to manage unique identifiers.

// Push data with auto-generated key
var pushResult = await Firebase.RealtimeDb.Ref("messages").PushAsync(message);

// Get the generated key
if (pushResult.IsSuccess)
{
    var newKey = pushResult.Value; // e.g., "-NxYz123abc"
    Console.WriteLine($"Created message with key: {newKey}");
}

Get Data

Use GetAsync<T> to read data from a specific location. The data is automatically deserialized to your specified type.

// Get data with type-safe deserialization
var result = await Firebase.RealtimeDb.Ref("users/user1").GetAsync<User>();

if (result.IsSuccess && result.Value != null)
{
    var user = result.Value;
    Console.WriteLine($"Welcome, {user.Name}!");
}

Update Data

Use UpdateAsync to update specific fields without overwriting the entire object. Only the specified fields are modified.

// Update specific fields
await Firebase.RealtimeDb.Ref("users/user1").UpdateAsync(new { Status = "online" });

// Update multiple fields at once
await Firebase.RealtimeDb.Ref("users/user1").UpdateAsync(new
{
    Status = "online",
    LastActive = DateTime.UtcNow,
    LoginCount = 5
});

Remove Data

Use RemoveAsync to delete data at a specific location. This removes the node and all its children.

// Remove data at a path
await Firebase.RealtimeDb.Ref("users/user1").RemoveAsync();

// Remove with result handling
var result = await Firebase.RealtimeDb.Ref("messages/oldMessage").RemoveAsync();
if (result.IsSuccess)
{
    Console.WriteLine("Message deleted successfully");
}

Real-time Listeners

One of the most powerful features of Realtime Database is the ability to listen for data changes in real-time. Use OnValue to subscribe to changes at a specific location.

// Subscribe to real-time updates with query options
var unsubscribe = Firebase.RealtimeDb
    .Ref("messages")
    .OrderByChild("timestamp")
    .LimitToLast(50)
    .OnValue<Dictionary<string, Message>>(
        snapshot => {
            if (snapshot.Exists)
                _messages = snapshot.Value.Values.ToList();
            InvokeAsync(StateHasChanged);
        },
        error => Console.WriteLine(error.Message));

// Later, when component is disposed:
unsubscribe();

Available query methods:

  • OrderByChild(string childKey) - Order results by a child key
  • OrderByKey() - Order results by key
  • OrderByValue() - Order results by value
  • LimitToFirst(int limit) - Limit to the first N results
  • LimitToLast(int limit) - Limit to the last N results
  • StartAt(object value) - Start at a specific value
  • EndAt(object value) - End at a specific value
  • EqualTo(object value) - Filter to a specific value
Automatic Cleanup

Always store the unsubscribe function and call it when your component is disposed to prevent memory leaks. Consider using FirebaseComponentBase which handles this automatically with the Subscribe() method.

Server Values

Server values are placeholders that are resolved on the Firebase server. This ensures consistency across clients and eliminates issues with client clock differences.

ServerValue.Timestamp

Use ServerValue.Timestamp to set a field to the server's current timestamp. This is more reliable than using client-side timestamps.

// Set server timestamp
await Firebase.RealtimeDb.Ref("posts/post1").UpdateAsync(new {
    CreatedAt = ServerValue.Timestamp
});

// Use in a new object
await Firebase.RealtimeDb.Ref("events").PushAsync(new
{
    Type = "user_login",
    UserId = currentUserId,
    Timestamp = ServerValue.Timestamp
});

ServerValue.Increment

Use ServerValue.Increment for atomic counter operations. This ensures accurate counting even with concurrent updates.

// Atomic increment
await Firebase.RealtimeDb.Ref("counters/visits").SetAsync(ServerValue.Increment(1));

// Decrement by using negative value
await Firebase.RealtimeDb.Ref("inventory/item1/stock").SetAsync(ServerValue.Increment(-1));

// Increment by any amount
await Firebase.RealtimeDb.Ref("users/user1/points").SetAsync(ServerValue.Increment(100));

Transactions

Transactions allow you to perform atomic read-modify-write operations. This is essential when you need to update data based on its current value, especially with concurrent users.

// Transaction for atomic updates
var result = await Firebase.RealtimeDb
    .Ref("likes/post1")
    .TransactionAsync<int>(current => (current ?? 0) + 1);

if (result.IsSuccess)
{
    Console.WriteLine($"New like count: {result.Value}");
}

Transactions are ideal for:

  • Incrementing/decrementing counters
  • Toggling boolean values
  • Appending to arrays
  • Any update that depends on the current value
// More complex transaction example
var result = await Firebase.RealtimeDb
    .Ref("games/game1/players")
    .TransactionAsync<List<string>>(currentPlayers =>
    {
        var players = currentPlayers ?? new List<string>();
        if (!players.Contains(currentUserId))
        {
            players.Add(currentUserId);
        }
        return players;
    });

Presence Detection

Presence detection allows you to track when users are online or offline. This is built on Firebase's OnDisconnect feature, which executes operations when a client disconnects.

Setting Online Status

Mark a user as online when they connect to your application.

// Set online status
await Firebase.RealtimeDb.Ref($"presence/{userId}").SetAsync(new { Status = "online" });

OnDisconnect for Offline Status

Use OnDisconnect to automatically update the user's status when they disconnect (close the browser, lose connection, etc.).

// Set offline status on disconnect
await Firebase.RealtimeDb
    .Ref($"presence/{userId}")
    .OnDisconnect()
    .SetAsync(new { Status = "offline", LastSeen = ServerValue.Timestamp });

Complete presence system example:

// Full presence detection setup
public async Task SetupPresence(string userId)
{
    var presenceRef = Firebase.RealtimeDb.Ref($"presence/{userId}");

    // Set online status
    await presenceRef.SetAsync(new
    {
        Status = "online",
        LastSeen = ServerValue.Timestamp
    });

    // Configure offline status for when user disconnects
    await presenceRef
        .OnDisconnect()
        .SetAsync(new
        {
            Status = "offline",
            LastSeen = ServerValue.Timestamp
        });
}
Automatic Reconnection

Firebase automatically handles reconnection when a user's connection is temporarily lost. The OnDisconnect operations are only executed when the connection is truly terminated, not during brief network interruptions.