SDK
SDK Dart Null Safety v3.x
2

Getting Started with Kuzzle and Flutter #

This section deals with Kuzzle v2, the Dart SDK v2 and Flutter. We will create documents in Kuzzle and subscribe to document notifications to develop a realtime chat.

Requirements #

Use the Kuzzle SDK in your flutter app #

You can find the kuzzle package on pub.dev. (https://pub.dev/packages/kuzzle)

The pubspec.yaml file manages the assets and dependencies for a Flutter app. In pubspec.yaml, add kuzzle (2.0.1 or higher) to the dependencies list:

dependencies:
  flutter:
    sdk: flutter
  kuzzle: ^2.0.1

Then run flutter pub get to install the new dependency

$ flutter pub get

App entry point #

Let's start with the main:

main.dart:

import 'package:flutter/material.dart';
import 'package:flutter_getting_started/login.dart';
void main() {
  runApp(Login());
}

Set a username #

Here we will ask the user to enter its username. To keep it simple, this does not use the authentication system of Kuzzle but feel free to implement it for a better usage.

login.dart:

import 'package:flutter/material.dart';
import 'package:flutter_getting_started/chat.dart';
class Login extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Chat app',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: LoginPage(title: 'Chat app'),
    );
  }
}
class LoginPage extends StatefulWidget {
  LoginPage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _LoginPage createState() => _LoginPage();
}
class _LoginPage extends State<LoginPage> {
  final _loginController = TextEditingController();
  @override
  void dispose() {
    _loginController.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.only(bottom: 8),
            child: Form(
              child: Column(
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.only(left: 8),
                    child: TextField(
                      controller: _loginController,
                      decoration: InputDecoration(
                        hintText: 'Username',
                      ),
                    ),
                  ),
                  RaisedButton(
                    onPressed: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(builder: (context) => Chat(_loginController.text)),
                      );
                    },
                    child: Text('Next'),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    ),
      );
}

The chat #

We have to connect the server so that our client can interact with it (note that the host is the API of an android emulator in this example).

In our chat.dart let's import the sdk:

import 'package:kuzzle/kuzzle.dart';

Then we will establish the connection to kuzzle and create, if they don't exist, the index and collection of our chat. We will also fetch messages sorted by creation date, and subscribe to the same collection to receive new messages in realtime.

Create index and collection #

So first let's write a method which will create the index/collection if it does not exist:

void _initData() async {
  // Check if 'chat' index exists
  if (!(await _kuzzle.index.exists('chat'))) {
    // If not, create 'chat' index and 'messages' collection
    await _kuzzle.index.create('chat');
    await _kuzzle.collection.create('chat', 'messages');
}
}

Get existing messages #

The method fetchMessage() will search for the first hundred newest messages. It will then update the state of the widget to store those messages in the messages variable.

void _fetchMessages() async {
  // Call the search method of the document controller
  final results = await this._kuzzle.document.search(
    'chat', // Name of the index
    'messages', // Name of the collection
    query: {
      'sort': {
        '_kuzzle_info.createdAt': {'order': 'asc'}
      }
    }, // Query => Sort the messages by creation date
    size: 100, // Options => get a maximum of 100 messages
  );
  // Add messages to our array after formating them
  setState(() {
    this._messages = results.hits
      .map((message) => Message.fromJson(message))
      .toList();
  });
}

Receive new messages in realtime #

And finally the method to subscribe to our collection. It will call the Kuzzle's realtime controller to allow us to receive real-time notifications on message creations. New messages received that way will then be added to our list of previously fetched messages, and rendered in our list, by updating the state.

void _subscribeToNewMessages() async {
  // Call the subscribe method of the realtime controller and receive the roomId
  // Save the id of our subscription (we could need it to unsubscribe)
  _roomId = await _kuzzle.realtime.subscribe(
    'chat', // Name of the index
    'messages', // Name of the collection
    {}, // Filter
    (notification) {
    if (notification.action != 'create') return;
    if (notification.controller != 'document') return;
    setState(() {
      _messages.add(Message.fromJson(notification.result));
    });
  }, subscribeToSelf: true);
}

Call previous written methods #

Then we are going to call all of those methods in the initState method of the State of our StatefulWidget:

@override
void initState() {
  _kuzzle = Kuzzle(WebSocketProtocol(Uri(
    scheme: 'ws',
    host: '10.0.2.2',
    port: 7512,
  )));
  // Etablish the connection
  _kuzzle.connect().then((_) {
    _initData();
    _fetchMessages();
    _subscribeToNewMessages();
  });
  super.initState();
}

As you can see we use a model to retrieve each message from the JSON Response to a Dart class.

Here:

this._messages = results.hits
  .map((message) => Message.fromJson(message))
  .toList();

And here:

_messages.add(Message.fromJson(notification.result));

So here is the Dart class used for this model

message.dart:

class Message {
  final String id;
  final String username;
  final String value;
  final int createdAt;
  Message({this.id, this.username, this.value, this.createdAt});
  factory Message.fromJson(Map<String, dynamic> json) => Message(
    id: json['_id'],
    username: json['_source']['username'],
    value: json['_source']['value'],
    createdAt: json['_source']['_kuzzle_info']['createdAt'],
  );
}

Display everything #

Finally let's make a view to display everything and add an input to be able to send a message in our build() method:

chat.dart:

@override
Widget build(BuildContext context) => Scaffold(
  appBar: AppBar(
    title: Text(widget.title),
  ),
  body: Center(
    child: Column(
      children: <Widget>[
        Expanded(
          child: Container(
            width: double.infinity,
            child: ChatView(
              messages: _messages,
            ),
          ),
        ),
        Padding(
          padding: const EdgeInsets.only(bottom: 8),
          child: Form(
            child: Row(
              children: <Widget>[
                Expanded(
                  child: Padding(
                    padding: const EdgeInsets.only(left: 8),
                    child: TextField(
                      controller: _chatController,
                    ),
                  ),
                ),
                FlatButton(
                  onPressed: () async {
                    await _kuzzle.document.create('chat', 'messages', {
                      'username': widget.username,
                      'value': _chatController.text
                    });
                    _chatController.clear();
                  },
                  child: Icon(Icons.send),
                ),
              ],
            ),
          ),
        ),
      ],
    ),
  ),
);

And chat_view.dart:

import 'package:intl/intl.dart';
import 'package:flutter/material.dart';
import 'package:flutter_getting_started/message.dart';
class ChatView extends StatefulWidget {
  ChatView({Key key, this.title, @required this.messages}) : super(key: key);
  final String title;
  final List<Message> messages;
  @override
  ChatViewState createState() => ChatViewState();
}
class ChatViewState extends State<ChatView> {
  List<Widget> messagesBox = [];
  ScrollController _scrollController = ScrollController();
  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
    });
    return Scaffold(
      body: ListView.builder(
        controller: _scrollController,
        itemCount: widget.messages.length,
        itemBuilder: (context, idx) {
          DateFormat format = DateFormat('yyyy-MM-dd KK:mm a');
          return Card(
            child: Stack(
              children: <Widget>[
                Column(
                  children: <Widget>[
                    ListTile(
                      leading: Icon(Icons.chat),
                      title: Stack(
                        children: <Widget>[
                          Text(
                            widget.messages[idx].username,
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                          Positioned(
                            top: 0,
                            right: 0,
                            child: Text(
                              format.format(
                                DateTime.fromMillisecondsSinceEpoch(
                                  widget.messages[idx].createdAt),
                              ),
                              style: TextStyle(
                                fontSize: 12,
                              ),
                            ),
                          ),
                        ],
                      ),
                      subtitle: Text(widget.messages[idx].value),
                    ),
                  ],
                ),
              ],
            ),
          );
        }),
    );
  }
}

Sending messages #

Finally let's see how to send a new message using the Kuzzle sdk:

chat.dart:

await _kuzzle.document.create('chat', 'messages', {
  'username': widget.username,
  'value': _chatController.text
});

For this we simply create a document in our messages collection with a username and a value. Once this document is created it will trigger a notification to all clients who subscribed to this collection and receive the message.

Where do we go from here? #

Now that you're more familiar with Kuzzle, dive even deeper to learn how to leverage its full capabilities: