[SWIFT] NotificationCenter & refresh tchat
Bonjour tout le monde,
Je suis actuellement en train de développer une messagerie interne et je bloque sur une fonctionnalité, celle d'aller chercher les messages reçus lorsqu'on est déjà sur la conversation.
Voici comment je m'y prend :
Lorsqu'un message est envoyé via l'appli, j'enregistre les infos dans ma bdd puis j'envois un push (via firebase) aux destinataires.
Mon push contient l'id de la conversation et l'id du dernier message envoyé..
D'après ce que j'ai compris sur le NotificationCenter, c'est que je peux surveiller un évènement et déclencher une fonction associé (j'ai bon ?)
J'ai donc créer mon évènement :
extension Notification.Name {
static let NouveauMessage = Notification.Name("NouveauMessage")
}
Dans mon AppDelegate, j'ai ajouté l'observer pour détecter lorsque mon évènement est appelé..
func applicationDidBecomeActive(_ application: UIApplication) {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(maFonctionDeTest), name: .NouveauMessage, object: nil)
}
// la fonction qui sera appelée à chaque push "nouveau message"
@objc func maFonctionDeTest(notification: Notification){
print(Notification)
}
Toujours dans AppDelegate, chaque notification reçu, je vérifie si c'est bien un message, si je suis bien sur le bon controller (ma page de tchat) et si c'est le cas, je déclenche un évènement "NouveauMessage"
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// verification si c'est bien un push "nouveau message", si je suis bien sur la bonne page (DiscussionController), etc etc,
let notificationCenter = NotificationCenter.default
notificationCenter.post(name: .NouveauMessage, object: self, userInfo: userInfo) // Envoi l'évènement "NouveauMessage"
}
Donc lorsque je reçois un push, je créé ma notification (.NouveauMessage) qui passe bien dans "maFonctionDeTest"..
C'est là que je m'y perds..
1/ Déjà, plutôt que d'utiliser maFonctionDeTest, est-ce-que je peux utiliser une fonction qui se trouve directement dans mon controller (ma page de tchat) ?
2/ Est-ce-que je peux remplir les variables de ce controller ?
Je m'explique..
Mon controller ressemble à ça :
class DiscussionController: UIViewController, etc etc {
var IdDiscussion = "0"
override func viewDidLoad(){
// mon code, etc etc
chargeMessage()
}
func chargeMessages(){
// requête pour récupérer les derniers messages
// ici j'ai besoin de l'IdDiscussion
// ça fonctionne très bien quand je le remplis via prepare(for segue)
// mais pas depuis AppDelegate
}
}
Lorsque je suis dans Appdelegate, comment faire pour remplir la variable IdDiscussion de mon controller (DiscussionController) ?
J'ai essayé avec :
DiscussionController().IdDiscussion = "10"
Mais il ne le prend pas en compte et reste toujours à zéro
Merci de votre aide
Réponses
Oui, il s'agit de signaux globaux à l'application.
Tu postes un signal dans le NotificationCenter et il avertit tous ses abonnés.
Déjà, il existe une version de addObserver() qui utilise une closure. Je trouve cela plus pratique, et moins "Objective-C".
Ensuite, il me semble que la seule chose que devrait faire la UIApplication sur la réception de la notification est éventuellement d'amener le DiscussionController au premier plan.
Ce qui amène au deuxième point: il peut tout à fait y avoir plusieurs abonnés à une même notification. S'il n'existe qu'une seule instance de DiscussionController, alors il devrait s'y abonner pour savoir qu'un nouveau message est arrivé, récupérer son id et l'afficher.
En théorie oui, tu pourrais écrire:
NotificationCenter.default.addObserver(discussionController, selector: #selector(didReceiveNewMessage), name: .NouveauMessage, object: nil).
Mais ça ne présente pas d'intérêt: le DiscussionController devrait s'abonner lui-même.
C'est normal.
Là tu instancies un nouveau DiscussionController, mais tu ne le présentes pas. Il faut que tu conserves une référence vers le DiscussionController courant:
Deux choix s'offrent à toi:
1. soit à tout moment, il n'existe qu'une seule instance de DiscussionController et lui passer un nouveau idDiscussion rafraîchit son contenu. Utilise
var idDiscussion { didSet {} }
pour cela.2. soit quand on change de discussion, on crée un nouveau DiscussionController avec cette discussion, on retire celui qui est actuellement présenté puis on présente le nouveau.
Je partirais plutôt sur la première solution, parce que de toute façon, de nouveaux messages peuvent arriver, et il faudra rafraichir l'écran. Le DiscussionController doit avoir un moyen de récupérer les messages.
Mais ça se discute, parce que par exemple, tu pourrais avoir plusieurs discussions ouvertes en même temps, et ça a le côté pratique de laisser la discussion dans l'état où l'utilisateur l'a laissée.
Quand tu dis "il avertit tous ses abonné", je m'abonne à un signal avec addObserver() .. on est d'accord ? ^^
Dans mon utilisation, j'étais parti sur ce genre de comportement :
Il existe bien qu'une instance de DiscussionController.
C'est ce que je m'étais dit, j'attendais confirmation ^^
private var discussionController: DiscussionController?
Mais cette déclaration, je l'a met où ? Dans mon AppDelegate nan ?
Le but c'est que mon DiscussionController soit disponible de partout si je comprend bien ?
Ouai, c'est le comportement choisi ici
Merci de ton aide en tout cas ^^
Je retourne tester tout ça
Et rien à voir mais c'était une mauvaise idée de mettre l'ajout de l'observer dans applicationDidBecomeActive
Je l'ai déplacé dans didFinishLaunchingWithOptions
Comme ça je ne l'ai qu'une fois..
Pcq si je met l'application en background et que je reviens dessus, il re-ajoute l'observer.. du coup il passe 2x dedans quand je reçois une notification "NouveauMessage".
J'suppose que j'aurai pu aussi l'enlever quand l'application passe en background..
ça revient au même au final.. (dites moi si j'met trompe ^^)
Bon, du coup j'ai réorganiser un peu le code par contre, je me heurte au soucis évoqué plus haut, du fait que je re-déclare une nouvelle instance de mon controller..
Lorsque je passe dans ma fonction pour récupérer mes nouveaux messages, impossible de refresh la tablebview (qui est à nil), ce qui est normal, vu que c'est un nouveau controller et pas celui qui est affiché..
Je pense que c'est cette solution la plus adaptée mais je n'ai aucune idée de comment faire ça..
Est-ce-que tu aurais un exemple de comment faire concrètement ?
En gros, je cherche à appeler la fonction chargeMessages() qui est dans ma class DiscussionController depuis AppDelegate mais en ayant accès à la tableview présente dans la class.
[Edit]
Je continue mes recherches et j'ai l'impression que je cherche à faire un truc comme ça :
NotificationCenter.default.addObserver(DiscussionController.self, selector: #selector(DiscussionController.recupMessages), name: .NouveauMessage, object: nil)
Je le comprend comme : J'ajoute un observer .NouveauMessage sur DiscussionController qui déclenchera la fonction recupMessages
Est-ce-que j'ai bon déjà dans mon cheminement ?
En testant je me retrouve avec une erreur :
unrecognized selector sent to class
J'continue de chercher mais j'sais pas si je dois chercher à résoudre ce soucis et ensuite j'aurai accès à ma fonction, ma tableview, etc etc ou si je fais fausse route car j'y aurai quand même pas accès
Oui, c'est ça.
Dans l'objet parent du DiscussionController. Et je crois comprendre que c'est l'AppDelegate chez toi.
(mauvais idée dans l'absolu, mais ne changeons pas de sujet).
D'ailleurs ça va être assez ennuyeux parce que tu vas devoir écrire un truc du genre:
Ce n'est pas le code exact, mais tu vois l'idée: comme le Main.storyboard est instancié automatiquement, il faut ce moyen assez dégueulasse pour obtenir une référence sur les objets.
Non, pas de partout, juste de l'objet parent. Il doit garder une référence pour pouvoir y accéder.
L'AppDelegate n'est pas un fourre-tout.
Si tu écris ça:
alors tu t'y prends mal, parce qu'il y a alors un couplage fort avec AppDelegate.
Non, tu n'as pas besoin, parce qu'en background la notification ne sera pas émise.
Tu dois pouvoir abonne l'AppDelegate dans sa méthode init() tout simplement. Tu es sûr qu'elle ne sera appelée qu'une fois ;-)
Pour préciser: l'AppDelegate n'a pas à accéder à la tableview.
Oui.
C'est normal, ce n'est pas la classe DiscussionController qui doit s'abonner, mais une instance de cette classe.
J'ai l'impression que tu confonds classe et instance.
La classe, c'est le moule qui permet de fabriquer les objets.
L'instance, c'est l'exemplaire de l'objet.
Je suis pas sûr..
Je vais essayé de t'en dire plus sur l'appli :
Quand je l'ouvre, j'ai une page de login, elle me renvoi vers un TabBarController (3 icônes en bas)
Le dernier m'affiche la liste des discussions et lorsque j'en sélectionne une, j'arrive enfin sur mon DiscussionController
Du coup, j'ai du mal à savoir ce que t'appelle "l'objet parent du DiscussionController" dans ce cas là..
J'ai juste mis ce code dans AppDelegate parce que c'est ici que ça me semblait le plus approprié, pour gérer le déclenchement du NotificationCenter lorsque je reçois un push "nouveau message"
Ouai mais ce que je voulais dire c'est que quand je mettez l'application en background et que je l'a remettez au premier plan, je repassais dans applicationDidBecomeActive et du coup, je re-créai l'observer.
Quand je recevais mon push, l'observer se déclenchait autant de fois que j'avais ré-ouvert mon appli..
Du coup en effet, je l'ai déplacé pour qu'il ne se créer qu'une seule fois et c'est réglé ^^
[Edit]
Je vais tester des trucs avec le code de ton dernier message mais je pense qu'il va y avoir un soucis en faisant comme ça..
Bon, j'ai du un peu bidouiller mais ça fonctionne..
J'trouve ma solution dégueulasse mais pour un vendredi soir, ça ira bien
1/ J'ai créé une variable globale (vous l'a voyez venir la bidouille ?)
var observerEnPlace = false
2/ Dans AppDelegate
3/ Dans viewDidLoad sur DiscussionController
Je passe par une variable globale que je change quand je crée mon observer, comme ça à chaque fois que je reviens sur mon DiscussionController, il ne recrée pas l'observer.
Je suis pas fan de cette solution mais bon..
J'ai essayé de créer l'observer et de le supprimer avec la méthode deinit, j'ai dû mal m'y prendre parce qu'il ne supprimait jamais l'observer, ça m'a gonflé donc je suis passé par la variable globale dégueulasse
Je crois que je vais me contenter de ça pour le moment ^^
En tout cas, merci de ton aide, je m'en serai pas sorti sinon
Une solution plus propre est de s'abonner dans
viewWillAppear()
et se désabonner dansviewWillDisappear()
. Ainsi il ne chargera pas inutilement les données s'il n'est pas visible.ah bah je repassais ici pour dire que c'est ce que j'avais fait ^^
La dernière fois j'avais du mal à supprimer l'observer du coup je tournais en rond mais après un petit week-end de repos et les idées plus claires, j'y suis arrivé ^^
Merci de ton aide