I'm building a chat app. Each channel has many messages. I am building the channel list view where I want to display all the channels sorted by the most recent message sent at per channel.
Each time a message is sent or received I would like to keep the channel.latestMessageUpdatedAt up to date so that I can sort channels later.
I would like to separate concerns and not have to remember to update channels each time messages are updated.
My strategy is to update the channel inside the listener to the message realm, but I get the following error
Error: Wrong transactional state (no active transaction, wrong type of transaction, or transaction already in progress)
const ChannelSchema = {
name: "channel",
primaryKey: "id",
properties: {
id: "string",
latestMessageUpdatedAt: "date",
},
};
const MessageSchema = {
name: "message",
primaryKey: "id",
properties: {
id: "string",
channelId: "string",
text: "string",
updatedAt: "date",
},
};
const realm = await Realm.open({
path: "default.realm",
schema: [ChannelSchema, MessageSchema],
schemaVersion: 0,
});
const messagesRealm = realm.objects("message");
messagesRealm.addListener((messages, changes) => {
for (const index of changes.insertions) {
const channel = realm.objectForPrimaryKey(
"channel",
messages[index].channelId
);
if (!channel) continue;
const message = messages[index];
channel.latestMessageUpdatedAt = new Date();
}
});
I've checked the docs there seems to be no reason why this wouldn't be possible.
Perhaps there is a better way of having this computed field.
Note I thought about having embedded objects / a list of messages on the channel but the number of messages could be up to 10k, I don't want that all returned at once into memory.
I've also tried doing
realm.write(() => {
channel.latestMessageUpdatedAt = new Date();
});
but I get the error that a transaction is already in progress.
The OP requested a Swift solution so I have two: which one is used depends on the data set and coding preference for relationships. There is no automatic inverse relationship in the question but wanted to include that just in case.
1 - Without LinkingObjects: a manual inverse relationship
Let's set up the models with a 1-Many relationship from a channel to messages and then a single inverse relationship from a message back to it's parent channel
Then after we populate Realm we have some objects that look like this - keeping in mind that different channels will have had messages added at different times
It would look like this because: suppose a user posts a message to channel 0, which would be message 1. Then a day later another user posts a message to to channel 2, which would be message 2. Then, on another day, a user posts a message to channel 0 which would be message 3. Etc etc
Keeping in mind that while Realm objects are unsorted, List objects always maintain their order. So the last element in each channels list is the most current message in that channel.
From there getting the channels sorted by their most recent message is a one liner
If you now iterate over messages to print the channels, here's the output. Note: This is ONLY for showing the data has already been retrieved from Realm and would not be needed in an app.
Then you say to yourself "self, wait a sec - the third message was in channel 0! So why output channel 1 as the last item?"
The reasoning is that the OP has a requirement that channels should only be listed once - therefore since channel 0 was already listed, the remaining channel is channel 1.
2 - With LinkingObjects: Automatic inverse relationship
Take a scenario where LinkingObjects are used to automatically create the backlink from the messages object back to the channel e.g. reverse transversing the object graph
the thought process is similar but we have to lean on Swift a little to provide a sort. Here's the one liner
What were doing here is querying the channels and using the msgDate property from the last message object in each channels list to sort the channels. and the output is the same
The only downside here is this solution will have larger memory impact but adds the convenience of automatic reverse relationships through LinkingObjects
3 Another option
Another options to add a small function and a property to the Channel class that both adds a message to the channels
messagesListand also populates the 'lastMsgDate' property of the channel. Then sorting the channels is a snap. So it would look like thisWhen ever a message is added to the channel, the last message date is updated. Then sort channels by
lastMsgDateNote: I used Strings for message dates for simplicity. If you want to do that ensure it's a yyyymmddhhmmss format, or just use an actual Date property.