Any client device can connect to Live Queries independently
- Fixed #8 (saves dates as string for now instead of just not saving)
- Fixed #4
- Closes #7 too
Fixed an issues where connected Live Queries would never actually close stream with server (causing memory leaks) This eliminates any instance where a "disconnected" user would still be able to receive data from the server and vice versa. Please update to this version as soon as possible to avoid any security issues. The best I would suggest is to migrate the currently observed classes to a new class and delete the old one. You can easily do this by creating a new class and copying the data from the old class to the new one (via ParseCloud in your web interface like in Back4App).
More Fixes...
- Fixed issues where
GetCurrentUIser()
from Parse did NOT returnuserName
too. - Fixed issues with
Relations/Pointers
being broken.
Thank You!
Improvements on base Parse SDK.
- LogOut now works perfectly fine and doesn't crash app!
- SignUpWithAsync() will now return the Signed up user's info to avoid Over querying.
- Renamed some methods.
- Fixed perhaps ALL previous UNITY crashes.
(Will do previous versions later - I might never even do it )
Key Changes from v1:
- Now supports .NET 5, 6, 7, 8, and .NET MAUI.
- Uses
System.Net.WebSockets.Client
for better connectivity. - Replaced callbacks (CB) with Reactive Extensions (Rx.NET) for event handling.
- Full LINQ support integrated.
- Enhanced stability and MAUI compatibility.
Thanks :) to:
- JonMcPherson for original Parse Live Query dotnet code.
- Parse Community for Parse SDK.
- My Parse-SDK fork which this depends on.
Live Query provides real-time data sync between server and clients over WebSockets. When data changes on the server, subscribed clients are instantly notified.
- Ensure Live Query is enabled for your classes in the Parse Dashboard.
using Parse; // Parse
using Parse.LiveQuery; // ParseLQ
using System.Reactive.Linq; // For Rx
using System.Linq; // For LINQ
using System.Collections.ObjectModel;
// Check internet
if (Connectivity.NetworkAccess != NetworkAccess.Internet)
{
Console.WriteLine("No Internet, can't init ParseClient.");
return;
}
// Init ParseClient
var client = new ParseClient(new ServerConnectionData
{
ApplicationID = "YOUR_APP_ID",
ServerURI = new Uri("YOUR_SERVER_URL"),
Key = "YOUR_CLIENT_KEY" // or MasterKey
}, new HostManifestData
{
Version = "1.0.0",
Identifier = "com.yourcompany.yourmauiapp",
Name = "MyMauiApp"
});
client.Publicize(); // Access via ParseClient.Instance globally
// Create LiveQuery client
ParseLiveQueryClient LiveClient = new ParseLiveQueryClient();
void SetupLiveQuery()
{
try
{
var query = ParseClient.Instance.GetQuery("TestChat");
var subscription = LiveClient.Subscribe(query);
LiveClient.ConnectIfNeeded();
// Rx event streams
LiveClient.OnConnected
.Subscribe(_ => Debug.WriteLine("LiveQuery connected."));
LiveClient.OnDisconnected
.Subscribe(info => Debug.WriteLine(info.userInitiated
? "User disconnected."
: "Server disconnected."));
LiveClient.OnError
.Subscribe(ex => Debug.WriteLine("LQ Error: " + ex.Message));
LiveClient.OnSubscribed
.Subscribe(e => Debug.WriteLine("Subscribed to: " + e.requestId));
// Handle object events (Create/Update/Delete)
LiveClient.OnObjectEvent
.Where(e => e.subscription == subscription)
.Subscribe(e =>
{
Debug.WriteLine($"Event {e.evt} on object {e.objState.ObjectId}");
});
}
catch (Exception ex)
{
Debug.WriteLine("SetupLiveQuery Error: " + ex.Message);
}
}
Below are 15+ examples demonstrating LINQ queries, Rx usage, and their combination to handle Live Query events. We will show various scenarios on a hypothetical "TestChat" class with fields: Message
, Author
, CreatedAt
.
Legend:
- Simple: Basic query & subscription.
- Medium: Introduce some filtering & conditions.
- Robust: Complex queries, multiple conditions, ordering, and advanced Rx usage.
- Very Robust (LINQ+RX): Combining reactive operators & LINQ to handle intricate, real-time data flows.
var q = ParseClient.Instance.GetQuery("TestChat");
var sub = LiveClient.Subscribe(q);
LiveClient.OnObjectEvent
.Where(e => e.subscription == sub)
.Subscribe(e => Debug.WriteLine("New Event: " + e.evt));
LiveClient.ConnectIfNeeded();
var q = ParseClient.Instance.GetQuery("TestChat")
.WhereEqualTo("Author", "John");
var sub = LiveClient.Subscribe(q);
LiveClient.OnObjectEvent
.Where(e => e.subscription == sub)
.Subscribe(e => Debug.WriteLine("John-related event: " + e.objState.ObjectId));
DateTime limitDate = DateTime.UtcNow.AddDays(-1);
var q = ParseClient.Instance.GetQuery("TestChat")
.WhereGreaterThan("CreatedAt", limitDate);
var sub = LiveClient.Subscribe(q);
LiveClient.OnObjectEvent
.Where(e => e.subscription == sub)
.Subscribe(e => Debug.WriteLine("Recent message event: " + e.objState.ObjectId));
var q = ParseClient.Instance.GetQuery("TestChat")
.WhereStartsWith("Author", "A")
.WhereExists("Message");
var sub = LiveClient.Subscribe(q);
LiveClient.OnObjectEvent
.Where(e => e.subscription == sub)
.Subscribe(e => Debug.WriteLine("A's event: " + e.objState.ObjectId));
var q = ParseClient.Instance.GetQuery("TestChat")
.WhereNotEqualTo("Author", "SpamBot")
.WhereGreaterThanOrEqualTo("CreatedAt", DateTime.UtcNow.AddHours(-2))
.OrderByDescending("CreatedAt")
.Limit(50);
var sub = LiveClient.Subscribe(q);
LiveClient.OnObjectEvent
.Where(e => e.subscription == sub)
.Subscribe(e =>
{
// Possibly transform object to your model
Debug.WriteLine("Robust event: " + e.objState.ObjectId);
});
var q = ParseClient.Instance.GetQuery("TestChat")
.WhereNotContainedIn("Author", new[] { "BannedUser1", "BannedUser2" })
.WhereMatches("Message", "urgent", "i"); // Case-insensitive regex
var sub = LiveClient.Subscribe(q);
LiveClient.OnObjectEvent
.Where(e => e.subscription == sub)
.Subscribe(e => Debug.WriteLine("Urgent event: " + e.objState.ObjectId));
LINQ Example 7 (Robust): Chain multiple queries using union (not built-in, but simulate using multiple subs)
// Query 1: Messages from Author = "TeamLead"
var q1 = ParseClient.Instance.GetQuery("TestChat").WhereEqualTo("Author", "TeamLead");
// Query 2: Messages containing "ProjectX"
var q2 = ParseClient.Instance.GetQuery("TestChat").WhereMatches("Message", "ProjectX");
var sub1 = LiveClient.Subscribe(q1);
var sub2 = LiveClient.Subscribe(q2);
LiveClient.OnObjectEvent
.Where(e => e.subscription == sub1 || e.subscription == sub2)
.Subscribe(e => Debug.WriteLine("TeamLead/ProjectX Event: " + e.objState.ObjectId));
LiveClient.OnConnected.Subscribe(_ => Debug.WriteLine("Connected via Rx."));
LiveClient.OnDisconnected.Subscribe(info => Debug.WriteLine("Disconnected via Rx."));
LiveClient.ConnectIfNeeded();
LiveClient.OnError
.Retry(3) // Try reconnect or re-subscribe up to 3 times
.Subscribe(ex => Debug.WriteLine("Error after retries: " + ex.Message));
var subscription = LiveClient.Subscribe(ParseClient.Instance.GetQuery("TestChat"));
LiveClient.OnObjectEvent
.Where(e => e.subscription == subscription && e.evt == Subscription.Event.Create)
.Buffer(TimeSpan.FromSeconds(5)) // Collect events for 5s
.Subscribe(batch =>
{
Debug.WriteLine($"Received {batch.Count} new messages in last 5s.");
});
LiveClient.ConnectIfNeeded();
var sub = LiveClient.Subscribe(ParseClient.Instance.GetQuery("TestChat"));
LiveClient.OnObjectEvent
.Where(e => e.subscription == sub && e.evt == Subscription.Event.Update)
.Throttle(TimeSpan.FromSeconds(2)) // If multiple updates occur quickly, take the last after 2s
.Subscribe(e => Debug.WriteLine("Throttled update event: " + e.objState.ObjectId));
var sub = LiveClient.Subscribe(ParseClient.Instance.GetQuery("TestChat"));
var events = LiveClient.OnObjectEvent.Where(e => e.subscription == sub);
var errors = LiveClient.OnError;
events.Merge(errors.Select(ex => new ObjectEventArgs { evt = Subscription.Event.Enter, objState = null }))
.Subscribe(e =>
{
if (e.objState == null)
Debug.WriteLine("Merged: Error event occurred.");
else
Debug.WriteLine("Merged: Normal object event " + e.objState.ObjectId);
});
LiveClient.ConnectIfNeeded();
var replayed = LiveClient.OnObjectEvent.Replay(1);
replayed.Connect(); // Start replaying
var subA = LiveClient.Subscribe(ParseClient.Instance.GetQuery("TestChat").WhereEqualTo("Author", "AUser"));
var subB = LiveClient.Subscribe(ParseClient.Instance.GetQuery("TestChat").WhereEqualTo("Author", "BUser"));
var combined = LiveClient.OnObjectEvent.Where(e => e.subscription == subA || e.subscription == subB);
combined.Subscribe(e => Debug.WriteLine("Replayed combined event: " + e.objState.ObjectId));
LiveClient.ConnectIfNeeded();
Rx+LINQ Example 1 (Very Robust): Filter events in-flight with LINQ, buffer and process after a condition
Scenario: We only want messages with "Critical" keyword and Author != "SystemBot", batch them every 10s.
var q = ParseClient.Instance.GetQuery("TestChat");
var s = LiveClient.Subscribe(q);
LiveClient.OnObjectEvent
.Where(e => e.subscription == s && e.evt == Subscription.Event.Create)
.Select(e => e.objState)
.Where(o => o.ContainsKey("Message") && ((string)o["Message"]).Contains("Critical"))
.Where(o => (string)o["Author"] != "SystemBot")
.Buffer(TimeSpan.FromSeconds(10))
.Where(batch => batch.Count > 0)
.Subscribe(batch =>
{
// LINQ over batch
var sorted = batch.OrderByDescending(o => (DateTime)o["CreatedAt"]);
foreach (var msg in sorted)
Debug.WriteLine("Critical Msg: " + msg["Message"]);
});
LiveClient.ConnectIfNeeded();
Scenario: Group updates by Author every 5s and print how many updates each author did.
var q = ParseClient.Instance.GetQuery("TestChat");
var s = LiveClient.Subscribe(q);
LiveClient.OnObjectEvent
.Where(e => e.subscription == s && e.evt == Subscription.Event.Update)
.Select(e => e.objState)
.GroupBy(o => (string)o["Author"])
.SelectMany(group =>
group.Buffer(TimeSpan.FromSeconds(5))
.Select(list => new { Author = group.Key, Count = list.Count })
)
.Subscribe(authorGroup =>
{
Debug.WriteLine($"{authorGroup.Author} made {authorGroup.Count} updates in the last 5s");
});
LiveClient.ConnectIfNeeded();
Rx+LINQ Example 3 (Very Robust): Merge two queries, distinct by ObjectId, and order results on the fly
Scenario: We want to combine messages from Authors "X" and "Y", remove duplicates, sort by CreatedAt descending, and only act on the top 10 recent unique messages each 10s.
var qX = ParseClient.Instance.GetQuery("TestChat").WhereEqualTo("Author", "X");
var qY = ParseClient.Instance.GetQuery("TestChat").WhereEqualTo("Author", "Y");
var sX = LiveClient.Subscribe(qX);
var sY = LiveClient.Subscribe(qY);
LiveClient.OnObjectEvent
.Where(e => (e.subscription == sX || e.subscription == sY) &&
(e.evt == Subscription.Event.Create || e.evt == Subscription.Event.Update))
.Select(e => e.objState)
.Buffer(TimeSpan.FromSeconds(10))
.Select(batch => batch
.GroupBy(o => o.ObjectId)
.Select(g => g.First()) // distinct by ObjectId
.OrderByDescending(o => (DateTime)o["CreatedAt"])
.Take(10))
.Subscribe(top10 =>
{
foreach (var msg in top10)
Debug.WriteLine("Top message from X/Y: " + msg["Message"]);
});
LiveClient.ConnectIfNeeded();
public class MyLQListener : IParseLiveQueryClientCallbacks
{
public void OnLiveQueryClientConnected(ParseLiveQueryClient client)
{
Debug.WriteLine("Client Connected");
}
public void OnLiveQueryClientDisconnected(ParseLiveQueryClient client, bool userInitiated)
{
Debug.WriteLine("Client Disconnected");
}
public void OnLiveQueryError(ParseLiveQueryClient client, LiveQueryException reason)
{
Debug.WriteLine("LiveQuery Error: " + reason.Message);
}
public void OnSocketError(ParseLiveQueryClient client, Exception reason)
{
Debug.WriteLine("Socket Error: " + reason.Message);
}
}
This v3 version integrates LINQ and Rx.NET, enabling highly flexible and reactive real-time data flows with Parse Live Queries. Advanced filtering, buffering, throttling, and complex transformations are now possible with minimal code.
PRs are welcome!