Chat Flutter Minimaliste
Ce document considère que vous avez suivi le document Installer les composants.
A la fin du précedent document vous devez avoir copié le fichier example_simple_chat.dart dans le fichier main.dart et lancé l'application.
cp ./lib/example_simple_chat.dart ./lib/main.dart
flutter run
Après avoir créé un compte, vous devriez obtenir l'écran suivant:
Si vous copiez le programme dans plusieurs répertoires ou machines en réseau local, et que vous créez le même compte à chaque fois, vous devriez être capable de converser entre les différents clients. Cet example est peu utile, mais permettra de vous familiariser avec l'API Discret.
Ce document ne décrit pas comment créer les écrans Flutter mais va se concentrer sur le code spécifique à l'API Discret.
Pour des raisons de simplicité, la gestion des erreurs est très basique, une application réelle devra mieux les gérer.
Preparation de l'API
l'API Discret nécessite trois paramètres pour être initalisée:
- Un identifiant unique pour l'application: une fois définit, cet identifiant ne devra jamais changer une fois l'application mise en production. Cet identifiant est utilisé pour dériver des secrets utilisés par Discret. Si cet identifiant change, les utisateurs ne pourront plus se connecter.
- Un modèle de données: définit les types de données qui peuvent être utilisés dans Discret.
- Un repertoire: où stocker des données.
Le debut du fichier initialise Discret
8 //...
9
10
11 // L'identifiant unique de l'application
12 // ignore: constant_identifier_names
13 const String APPLICATION_KEY = "github.com/discretlib/flutter_example_simple_chat";
14 void main() async {
15 //ne fonctionne pas sur smartphone,
16 // example_advanced_chat.dart fournit un version compatible avec les smartphone
17 String dataPath = '.discret_data';
18 String datamodel = """chat {
19 Message {
20 content: String
21 }
22 }""";
23 await Discret().initialize(APPLICATION_KEY, dataPath, datamodel);
24
25 runApp(
26 const ChatApp(),
27 );
28 }
29
30 //
31 // Cette classe contient tous les appels à l'API.
32 //
33 class ChatState extends ChangeNotifier {
34 //...
les lignes 9 et 10 importent les deux packages requis par l'API.
La ligne 13 définit l'identifiant unique de l'application:
12 //...
13 const String APPLICATION_KEY = "github.com/discretlib/flutter_example_simple_chat";
14 //...
Les lignes 18 à 22 définissent le modèle de données qui pourra être utilisé dans l'application.
Ce modèle définit un espace de nom nommé chat qui contient l'entité Message, qui elle même contient un unique champ nommé content qui peut contenir une chaine de caractères.
17 //...
18 String datamodel = """chat {
19 Message{
20 content:String
21 }
22 }""";
23 //...
Vous pourrez en apprendre plus dans la section Schémas et Entités de la documentation.
Une fois l'API initialisée, l'application va démarrer et instancier la classe ChatState qui contient toutes les fonctions qui vont appeller l'API.
L'objet ResultMsg
Vous verrez dans la suite du document que la plupart des fonctions de l'API retournent l'object ResultMsg qui contient les champs suivants.
class ResultMsg{
int id,
bool successful,
String error,
String data,
}
- id est un champ technique sans utilité particulière pour l'utilisateur,
- successful indique si la fonction s'est effectuée avec succès,
- error contient le message d'erreur eventuel,
- data contient le résultat de l'appel.
Créer un compte et se connecter
La création d'un compte et la connection se font avec une unique fonction de l'API.
47 Future<ResultMsg> login(String userName, String password, bool create) async {
48 ResultMsg msg = await Discret().login(userName, password, create);
49 if (msg.successful) {
50 await initialise();
51 }
52 return msg;
53 }
La différence entre une création de compte et une connection réside dans la valeur de create:
- false: la fonction se connectera si le compte existe, et retournera une erreur si il n'existe pas
- true: la fonction va créer le compte et se connecter si il n'existe pas, et retournera une erreur si le compte existe déjà.
Si la connection est un succès, la fonction initialise(); est appelée. La librairie Discret ne démarre qu'après l'authentification, donc le ChatState ne peut pas être initialisé avant.
Initialisation du ChatState
La fonction d'initialisation est la suivante:
58 Future<void> initialise() async {
59 ResultMsg msg = await Discret().privateRoom();
60 privateRoom = msg.data;
61
62 eventlistener = Discret().eventStream().listen((event) async {
63 switch (event.type) {
64 case EventType.dataChanged:
65 await refreshChat();
66
67 default:
68 }
69 }, onDone: () {}, onError: (error) {});
70
71 refreshChat();
72 }
La ligne 60 récupère la Room privée de l'utilisateur. Le concept de Room définit le système d'autorisations utilisé par Discret pour synchroniser les données avec ses Pairs. Les données insérées dans une Room ne seront synchronisées qu'avec les Pairs ayant accès à cette Room.
Nous allons insérer les messages dans la Room Privée de l'utilisateur, donc lui seul sera capable d'y accéder. Si l'utilisateur se connecte sur plusieurs machines, les données seront synchronisées entre les machines.
Vous pourrez en apprendre plus dans la section Room de la documentation.
A partir de la ligne 62, nous allons écouter le flux d'évènements envoyé par Discret. Nous ne nous intéressons qu'à l'évènement EventType.dataChanged qui est envoyé quand Discret détecte que des données ont été modifiées.
Dans cet exemple, cet évènement est déclenché:
- quand vous insérez un message depuis cette instance de l'application
- quand des données d'autre instance de l'application sont synchronisées avec cette instance
Quand cet évènement est détecté, la fonction refreshChat() est appelée pout mettre à jours la liste des messages.
Vous pourrez en apprendre plus sur les différents évènements dans la section Évènements Système de la documentation.
Une fois l'initialisation terminée la fonction refreshChat() est appelée un première fois pour récupérer les messages qui ont pu être écrits lors d'une session précédente.
Récupérer les messages: la fonction refreshChat()
La fonction utilisée pour récupérer les messages est la suivante:
77 Future<void> refreshChat() async {
78 String query = """query {
79 res: chat.Message(
80 order_by(mdate asc, id asc),
81 after(\$mdate, \$id),
82 room_id = \$room_id
83 ) {
84 id
85 mdate
86 content
87 }
88 }""";
89 ResultMsg res = await Discret().query(
90 query, {"mdate": lastEntryDate, "id": lastId, "room_id": privateRoom});
91
92 if (!res.successful) {
93 print(res.error);
94 } else {
95 final json = jsonDecode(res.data) as Map<String, dynamic>;
96 final msgArray = json["res"] as List<dynamic>;
97
98 for (var msgObject in msgArray) {
99 var message = msgObject as Map<String, dynamic>;
100 var entry =
101 ChatEntry(message["id"], message["mdate"], message["content"]);
102 lastEntryDate = entry.date;
103 lastId = entry.id;
104
105 chat.add(entry);
106 }
107 notifyListeners();
108 }
109 }
La fonction envoie une requête de type query qui permet de récupérer des données depuis la base de donnée de Discret. Vous pourrez en apprendre plus sur les requêtes de sélection de données dans la section Requête de la documentation.
Vous pourrez noter que cette requête définit trois paramètres:
- $mdate
- $id
- $room_id
qui sont passés lors de l'appel à l'API:
ResultMsg res = await Discret().query(
query, {
"mdate": lastEntryDate,
"id": lastId,
"room_id": privateRoom
}
);
Envoyer un message
La fonction utilisée pour envoyer un message est la suivante:
113 Future<void> sendMessage() async {
114 if (chatController.text != "") {
115 String query = """mutate {
116 res : chat.Message{
117 room_id: \$room_id
118 content: \$content
119 }
120 }""";
121
122 ResultMsg res = await Discret().mutate(
123 query, {"room_id": privateRoom, "content": chatController.text});
124
125 if (!res.successful) {
126 print(res.error);
127 } else {
128 chatController.clear();
129 }
130 }
131 }
Nous utilisons une requête de type mutation pour insérer un tuple de l'entité chat.Message définie dans le modèle de données.
Vous noterez que room_id n'a pas été défini dans le modèle de données. C'est un champ système disponible pour toutes les entités.
$room_id et $message sont des paramètres de la requête qui sont passés lors de l'appel à l'API:
ResultMsg res = await Discret().mutate(
query, {
"room_id": privateRoom,
"content": chatController.text
}
);
Vous pourrez en apprendre plus sur l'insertion et la modification de données dans la section Mutations et Suppression de la documentation.