Writing Flutter Mobile Application with Meteor backend

Image for post
Image for post

Update: On May 1st, 2020 I just published dart_meteor_web, the web version of the dart_meteor package which working with flutter web.

For the past few years, Meteor was a rising star in creating real-time web applications. However this day its popularity has dropped down. Many users have migrated to another modern framework. But the Meteor itself yet still working well for small and medium-size projects, and many of them are still in the production live on the Internet.

Meteor natively supports mobile application via its embedded Cordova framework. It plays well for a web developer who wants to have a mobile application as an extra of their work. Since Cordova is based on WebView, its look and feel are not seems like the native mobile application. If you want to leverage your mobile app you may consider creating your app with Google’s Flutter and yet still able to connect to your existing Meteor backend.

  • Existing Meteor Web Application Experience
  • Starter Experience of Flutter Development

First, we will create a simple chat app that has a login feature with Meteor and then we will create a Flutter app that talks with our Meteor server.

  1. Clone an example “simple-meteor-chat” project from my Github repository: https://github.com/tanutapi/simple-meteor-chat
  2. Open your shell and run “meteor npm install” and then “meteor”.
  3. Open your browser to see the example application is working.

You should see something like this in your browser at http://localhost:3000

Image for post
Image for post

There are two user accounts built in the application.

  1. user1 and password1
  2. user2 and password2

Try to login with any user and send some messages.

Image for post
Image for post
After sent Hello world! message.

Everything is working well. Now it’s time to create our Flutter application.

Creating a Flutter application

With your favorite IDE, Visual Studio Code or Android Studio, create a new flutter project. Add dart_meteor as a dependency in “pubspec.yaml” file and do a “flutter pub get”

dependencies:   
dart_meteor: ^1.0.6

In the “main.dart”, simply create an instance of Meteor in the global scope just before the main() function so that it can be used in other modules.

import 'package:dart_meteor/dart_meteor.dart';
...
MeteorClient meteor = MeteorClient.connect(url: 'http://localhost:3000');void main() => runApp(MyApp());
...

Now let’s create a page that indicates that we are trying to make a connection to our Meteor server. This page does a simple thing, showing a circular progress indicator and a button that makes a connection retry immediately.

import 'package:flutter/material.dart';
import 'package:flutter_app/main.dart';
class ConnectingPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.white,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(),
SizedBox(
height: 12.0,
),
Text('Connecting to server...'),
SizedBox(
height: 12.0,
),
RaisedButton(
child: Text('Retry to connect now!'),
onPressed: () {
meteor.reconnect();
},
)
],
),
),
),
);
}
}

Then in our MyApp widget, we can detect that our Meteor client is connecting to the server by using a StreamBuilder with meteor.status().

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: StreamBuilder<DdpConnectionStatus>(
stream: meteor.status(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.connected) {
return StreamBuilder<Map<String, dynamic>>(
stream: meteor.user(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ChatPage();
}
return LoginPage();
},
);
}
return ConnectingPage();
}
return Container();
})
,
);
}
}

From the above code, if our DdpConnectionStatus was returned from the “meteor.status()” stream has a connected property equals true then we will present a login-page or chat-page based on another StreamBuilder with “meteor.user()”, else we will present a connecting-page. Let’s break down to see it more clearly. First, we use a StreamBuilder to make a selection between connecting-page and other pages.

StreamBuilder<DdpConnectionStatus>(
stream: meteor.status(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.connected) {
return ...
}
return ConnectingPage();
}
return Container();
})
,

And another StreamBuilder to make a selection between chat-page and login-page.

...StreamBuilder<Map<String, dynamic>>(
stream: meteor.user(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ChatPage();
}
return LoginPage();
},
);
...

In our login page, we will have two TextFields and one RaisedButton. When users press on the button we will make login to our server.

meteor
.loginWithPassword(_username, _password)

.catchError((err) {
if (err is MeteorError) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Error'),
content: Text(err.details),
actions: <Widget>[
FlatButton(
child: Text('Dismiss'),
onPressed: () {
Navigator.of(context).pop(0);
},
),
],
);
});
}

You must always catch an error when making a call to the server else if any error has been thrown your application will crash.

Also on the chat page, you have a delete-all-messages button. When users press that button you will make a method call to the server.

meteor.call('clearAllMessages', []).catchError((_) {});

Now let’s see an example on the chat page that has a subscription logic.

import 'package:dart_meteor/dart_meteor.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/main.dart';
class ChatPage extends StatefulWidget {
@override
_ChatPageState createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
final TextEditingController _textEditingControllerMessage =
TextEditingController();
SubscriptionHandler _sub;
Map<String, dynamic> _users;
@override
void initState() {
super.initState();
meteor.prepareCollection('messages');
_sub = meteor.subscribe('messages', []);
meteor.collections['users'].listen((data) {
_users = data;
});

}
@override
void dispose() {
_textEditingControllerMessage.dispose();
if (_sub != null) {
_sub.stop();
_sub = null;
}

super.dispose();
}
@override
Widget build(BuildContext context) {
return ...;
}
}

and a StreamBuilder that used to create a list of chat messages.

StreamBuilder(
stream: meteor.collections['messages'],
builder:
(context, AsyncSnapshot<Map<String, dynamic>> snapshot) {
if (snapshot.hasData) {
List<dynamic> messages = snapshot.data.values.toList();
messages.sort((x, y) {
return x['createdAt']['\$date']
.compareTo(y['createdAt']['\$date']);
});
if (messages.length > 0) {
return ListView.builder(
itemCount: messages.length,
itemBuilder: (context, idx) {
String from = messages[idx]['from'];
if (_users != null) {
from = _users[messages[idx]['from']]['username'];
}
return ListTile(
leading: Text(from),
title: Text(messages[idx]['msg']),
);
},
);

}
}
return ListTile(
title: Text('No chat message... Please send one.'),
);

This is how our Flutter application looks like.

Image for post
Image for post

The full source code of a demo Flutter application could be found at https://github.com/tanutapi/flutter_meteor_dart_demo. Don’t forget to run the Meteor application first, then run the Flutter application else you will see only the connecting-page.

I have demonstrated how to create a Flutter application using the dart_meteor package to make it able to connect to the existing Meteor application. Also the main logic on how to handle a connection status, user authentication, and subscriptions.

Personally, I am still seeing the chance that using Meteor as a backend is more beneficial than using the Google Firebase ecosystem. You can control your own MongoDB, real-time WebSocket connection, and server methods. Bye!

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store