SBSMChannelCollectionDelegate when receiving a new message triggers twice the .update event

(iOS/Swift) Using SBSMChannelCollectionDelegate when I receive a new message it updated twice the channel when the app is on foreground, the first time with the correct data, correct number of unread messages, etc. the second time it updates it has 0 new messages, but moving to background or closing and opening the app, fixes the channel data.
thought updating the SDK would fix the issue but it didn’t.
SDK before Update

  • SendBirdSDK (3.0.177)
  • SendBirdSyncManager (1.1.19):
    • SendBirdSDK (~> 3.0.156)
      SDK After Update
  • SendBirdSDK (3.0.184)
  • SendBirdSyncManager (1.1.23):
    • SendBirdSDK (~> 3.0.178)

@Cris_Warren Hi Cris, I’d appreciate if you can specify the exact steps to reproduce your issue.

1 Like

https://share.getcloudapp.com/NQugDxXe this is a video of the issue.

func collection(_ collection: SBSMChannelCollection, didReceiveEvent action: SBSMChannelEventAction, channels: [SBDGroupChannel]) {
    switch (action) {
        case .insert:
            break
        case .update:
            // This is getting called/triggered twice the first time with the correct data, second time it is like the channel was marked as read.
                break
        case .remove:
            break
        case .move:
            break
        case .clear:
            break
        case .none:
            break;
    }
}

Do you call markAsRead somewhere else? because SyncManager does not call markAsRead method. Can you check this for me?

1 Like

Hi Woo, I originally thought that was the issue, and I checked an placed breakpoints in the only place we call markAsRead but the channel is not being marked as read, just moving the app to the background and bringing it back to the foreground shows the correct channel data with the correct number of unread messages and everything, so when the event in collection: case .update: gets called twice the first time it has the correct data, the second time it is like if it was markAsRead but it wasn’t, and sending it to the background fixes the data in the channel.

How do you handle messages when .update action was called, specifically how did you display your cell with? you said that it was called twice, first with correct message and second with no message. Can you see if any difference on channel objects in those two calls?

1 Like

when .update is called we replace/update the channel object in our channel array, the first time channel object is correct with all the data(correct number of unread messages, etc.), second time it gets called the channel object has 0 unread messages even if they where not marked as read.

First Call
channel: <channel url: 4b68e, created at: 1585589464, last message: Test unread messages, msg created at: 1592437888703>

Last Message Optional(0x600002e38c40 - Message: Test unread messages, ID: 4613012829, Sender: Bdbdjdjjwnw Jdjdjd jdjdjd, Timestamp: 1592437888703)

channel unread Messages 1

Second Call

channel <channel url: 4b68e, created at: 1585589464, last message: Test unread messages, msg created at: 1592437888703>

Last Message Optional(0x600002e38c40 - Message: Test unread messages, ID: 4613012829, Sender: Bdbdjdjjwnw Jdjdjd jdjdjd, Timestamp: 1592437888703)

channel unread Messages 0

after a while, managerSync will update correctly, or when moving it to background which forces manager sync to update/sync the channels which will cause channel unread Messages to increase to 1

@Cris_Warren Thank you for the information. I will try and see if I can reproduce the same result

1 Like

We’ve also been having the issue on a different app, set up in a different way but same issue, channel looks like it was marked as read.

I wrote a test case that simulated the situation you described. Unfortunately, I was not able to reproduce the same result. Can you provide the code snippet that shows how you use SyncManager? Also, from the video, it seems like your app was foreground yet you receive push notification. Is this intentional?

1 Like
import Foundation
import SendBirdSDK
import SendBirdSyncManager

protocol SBDManagerDelegate: class {
func didReceiveNewMessage()
}

class SendBirdManager: NSObject {
static let shared = SendBirdManager()
weak var delegate: SBDManagerDelegate?

var channels = [SBDGroupChannel]()
var channelCollection: SBSMChannelCollection?

override init() {
    super.init()
}

deinit {
    channelCollection?.delegate = nil
    channelCollection?.remove()
}

func setUpSendBird() {

    SBDMain.initWithApplicationId(Const.kSendBirdAppId)
    connectToSendBird {
        SBDMain.setLogLevel(SBDLogLevel.none)
        SBDOptions.setUseMemberAsMessageSender(true)
        SBDMain.add(self as SBDUserEventDelegate, identifier: Const.kSendBirdAppId)
        SBDMain.add(self as SBDChannelDelegate, identifier: Const.kSendBirdAppId)
        if let user  = SBDMain.getCurrentUser() {
            let options = SBSMSyncManagerOptions()
            options.messageCollectionCapacity = 2147483647
            options.messageResendPolicy = .automatic
            options.maxFailedMessageCountPerChannel = 5
            SBSMSyncManager.setup(withUserId: user.userId, options: options)
            guard let query = SBDGroupChannel.createMyGroupChannelListQuery() else { return }
            query.includeEmptyChannel = true
            query.order = .chronological
            query.limit = 100
            self.channelCollection = SBSMChannelCollection(query: query)
            self.channelCollection?.delegate = self

            self.channelCollection?.fetch(completionHandler: { (error) in
                if error != nil {
                    print(error?.localizedDescription ?? "")
                }else{
                    self.sortChannels()
                }
            })
            SBDMain.add(self as SBDConnectionDelegate, identifier: self.description)
        }
    }
}


// Sendbird User functions
private func connectToSendBird(completion:@escaping () -> ()) {
    if objUserDM.first_name == "" { return }
    ConnectionManager.login(userId: UserDefaults.standard.getLoggedInUserId(), nickname: objUserDM.first_name + " " + objUserDM.last_name) { (user, error) in
        guard let strUrl = objUserDM.profilePhotosURL.first else {
            self.updateSendbirdProfile(profileUrl: nil)
            completion()
            return
        }

        self.updateSendbirdProfile(profileUrl: strUrl)
        completion()

        if let _: NSError = error {
            completion()
            return
        }
    }
}

func registerToken(_ token: Data) {
    SBDMain.registerDevicePushToken(token, unique: true) { (status, error) in
        if error == nil {
            if status == SBDPushTokenRegistrationStatus.pending {
            } else {
                print("---- nice --- done")
            }
        } else {
        }
    }
}
}


extension SendBirdManager: SBDUserEventDelegate, SBSMChannelCollectionDelegate {
func didUpdateTotalUnreadMessageCount(_ totalCount: Int32, totalCountByCustomType: [String : NSNumber]?) {
}

func collection(_ collection: SBSMChannelCollection, didReceiveEvent action: SBSMChannelEventAction, channels: [SBDGroupChannel]) {
    switch (action) {
    case .insert:
        // TODO: Insert channels on the list.
        for channel in channels{
            CoreDataManager.shared.deletePendingMessage(channelURL: channel.channelUrl)
                if let mediaMSG = channel.lastMessage as? SBDFileMessage{
                    self.cacheMessageImage(message: mediaMSG) {
                        SendBirdCache.shared.save(channels: [channel]) {
                            self.updateChannelList(ChannelURL: channel.channelUrl)
                        }
                    }
                }else{
                    SendBirdCache.shared.save(channels: [channel]) {
                        self.updateChannelList(ChannelURL: channel.channelUrl)
                    }
                }
            }

        break
    case .update:             // TODO: This Gets called 2X.

        for channel in channels{
            if channel.hiddenState == .unhidden{
                print(3000000000, channel)
                print(3000000000, channel.lastMessage)
                print(3000000000, channel.unreadMessageCount)
                 if let message = channel.lastMessage{
                                    CoreDataManager.shared.deletePendingMessage(channelURL: channel.channelUrl)
                                    NotificationCenter.default.post(name: Notification.Name(rawValue: channel.channelUrl), object: nil)
                                }
                                    if let mediaMSG = channel.lastMessage as? SBDFileMessage{
                                        self.cacheMessageImage(message: mediaMSG) {
                                            SendBirdCache.shared.save(channels: [channel]) {
                                                self.updateChannelList(ChannelURL: channel.channelUrl)
                                            }
                                        }
                                    }else{
                                        SendBirdCache.shared.save(channels: [channel]) {
                                            self.updateChannelList(ChannelURL: channel.channelUrl)
                                        }
                                    }
            }else{
                CoreDataManager.shared.deleteChannel(channelURL: channel.channelUrl)
                self.channels.removeAll(where: {$0.channelUrl == channel.channelUrl})
                self.sortChannels()
                self.delegate?.didReceiveNewMessage()
            }
        }
        
        break
    case .remove:
        // TODO: Remove channels on the list.
        for channel in channels{
            if let index = self.channels.firstIndex(where: {$0.channelUrl == channel.channelUrl}) {
                self.channels.remove(at: index)
            }
            CoreDataManager.shared.deleteChannel(channelURL: channel.channelUrl)
        }
        self.sortChannels()
        self.delegate?.didReceiveNewMessage()
        break
    case .move:
        // TODO: Move channels on the list.
        
        break
    case .clear:
        // TODO: Clear all channels.
        for channel in self.channels{
            CoreDataManager.shared.deleteChannel(channelURL: channel.channelUrl)
        }
        self.channels.removeAll()
        self.delegate?.didReceiveNewMessage()

        break
    case .none:
        break;
    @unknown default:
        break;
    }

}

func updateChannelList(ChannelURL: String){
    SendBirdCache.shared.fetchChannelWithURL(channelURL: ChannelURL) { (channel) in
        if channel != nil {
            self.channels.removeAll(where: {$0.channelUrl == channel!.channelUrl})
            if channel!.hiddenState == .unhidden{
                self.channels.append(channel!)
            }
        }
        self.sortChannels()
        self.delegate?.didReceiveNewMessage()
    }
}
}

extension SendBirdManager: SBDConnectionDelegate {
func didStartReconnection() {
    SBSMSyncManager.pauseSynchronize()
}

func didSucceedReconnection() {
    SBSMSyncManager.resumeSynchronize()
}

func didFailReconnection() {
}
   func didCancelReconnection() {
}

}

extension SendBirdManager: SBDChannelDelegate {
func channel(_ sender: SBDBaseChannel, didReceive message: SBDBaseMessage) {
    print(sender, message)

    // Update just the one channel
    updateChannelWithUrl(url: sender.channelUrl, completionHandler: { _ in
        self.delegate?.didReceiveNewMessage()
    })
}
    func logout(completionHandler: (() -> Void)?) {
        SBDMain.disconnect {
            SBSMSyncManager.clearCache()
            self.channels = []
            let userDefault = UserDefaults.standard
            userDefault.setValue(false, forKey: "sendbird_auto_login")
            userDefault.removeObject(forKey: "sendbird_dnd_start_hour")
            userDefault.removeObject(forKey: "sendbird_dnd_start_min")
            userDefault.removeObject(forKey: "sendbird_dnd_end_hour")
            userDefault.removeObject(forKey: "sendbird_dnd_end_min")
            userDefault.removeObject(forKey: "sendbird_dnd_on")
            userDefault.synchronize()

            UIApplication.shared.applicationIconBadgeNumber = 0

            if let handler: () -> Void = completionHandler {
                handler()
            }
        }
    }


    func unhideChannel(channel: SBDGroupChannel){
        if channel.hiddenState == .unhidden {return}
            channel.unhideChannel(completionHandler: { error in
                guard error == nil else {   // Error.
                    return
                }
                self.channels.append(channel)
            })
    }
func loadCachedChannels(){
    self.channels = SendBirdCache.shared.loadChannels()
    self.sortChannels()
    self.delegate?.didReceiveNewMessage()
}

as for the notifications yes, it is Intentional.

Any updates???
after updating my pods to

  • SendBirdSDK (3.0.185)
  • SendBirdSyncManager (1.1.24):
    • SendBirdSDK (~> 3.0.178)

issue is still there.
downgrading to this fixed the issue in most devices but some still experience it.

  • SendBirdSDK (3.0.177)
  • SendBirdSyncManager (1.1.19):
    • SendBirdSDK (~> 3.0.156)

Sorry for late response, I’m out of hands atm :sob: I will try to see more in details on this week

1 Like

thanks!!, is there someone we can contact or anyone who can help out fix this issue, since we have multiple projects with sendbird, and this one is being delayed by this issue.

@Cris_Warren I think you can contact solution engineers via https://dashboard.sendbird.com/settings/contact_us and we can start there to prioritize tasks! I appreciate for your patience.

1 Like

i met this issue too, the second time fetch .update event, the channel unread message count is been 0

i met this issue too

@Woo,Can you see my topic?

I will look into this more this week

1 Like

@Cris_Warren are you sure .update event calls twice? I just tested it and it seems like the delegate get called twice but .move and .update respectively

1 Like