diff --git a/examples_dart/bin/simple_client.dart b/examples_dart/bin/simple_client.dart
new file mode 100644
index 0000000..00070f7
--- /dev/null
+++ b/examples_dart/bin/simple_client.dart
@@ -0,0 +1,112 @@
+import 'package:logging/logging.dart';
+import 'package:moxxmpp/moxxmpp.dart';
+import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
+
+/// The JID we want to authenticate as.
+final xmppUser = JID.fromString('jane@example.com');
+
+/// The password to authenticate with.
+const xmppPass = 'secret';
+
+/// The [xmppHost]:[xmppPort] server address to connect to.
+/// In a real application, one might prefer to use [TCPSocketWrapper]
+/// with a custom DNS implementation to let moxxmpp resolve the XMPP
+/// server's address automatically. However, if we just provide a host
+/// and a port, then [TCPSocketWrapper] will just skip the resolution and
+/// immediately use the provided connection details.
+const xmppHost = 'localhost';
+const xmppPort = 5222;
+
+void main(List args) async {
+ Logger.root.level = Level.ALL;
+ Logger.root.onRecord.listen((record) {
+ print('${record.level.name}|${record.time}: ${record.message}');
+ });
+
+ // This class manages every aspect of handling the XMPP stream.
+ final connection = XmppConnection(
+ // A reconnection policy tells the connection how to handle an error
+ // while or after connecting to the server. The [TestingReconnectionPolicy]
+ // immediately triggers a reconnection. In a real implementation, one might
+ // prefer to use a smarter strategy, like using an exponential backoff.
+ TestingReconnectionPolicy(),
+
+ // A connectivity manager tells the connection when it can connect. This is to
+ // ensure that we're not constantly trying to reconnect because we have no
+ // Internet connection. [AlwaysConnectedConnectivityManager] always says that
+ // we're connected. In a real application, one might prefer to use a smarter
+ // strategy, like using connectivity_plus to query the system's network connectivity
+ // state.
+ AlwaysConnectedConnectivityManager(),
+
+ // This kind of negotiator tells the connection how to handle the stream
+ // negotiations. The [ClientToServerNegotiator] allows to connect to the server
+ // as a regular client. Another negotiator would be the [ComponentToServerNegotiator] that
+ // allows for connections to the server where we're acting as a component.
+ ClientToServerNegotiator(),
+
+ // A wrapper around any kind of connection. In this case, we use the [TCPSocketWrapper], which
+ // uses a dart:io Socket/SecureSocket to connect to the server. If you want, you can also
+ // provide your own socket to use, for example, WebSockets or any other connection
+ // mechanism.
+ TCPSocketWrapper(false),
+ )..connectionSettings = ConnectionSettings(
+ jid: xmppUser,
+ password: xmppPass,
+ host: xmppHost,
+ port: xmppPort,
+ );
+
+ // Register a set of "managers" that provide you with implementations of various
+ // XEPs. Some have interdependencies, which need to be met. However, this example keeps
+ // it simple and just registers a [MessageManager], which has no required dependencies.
+ await connection.registerManagers([
+ // The [MessageManager] handles receiving and sending stanzas.
+ MessageManager(),
+ ]);
+
+ // Feature negotiators are objects that tell the connection negotiator what stream features
+ // we can negotiate and enable. moxxmpp negotiators always try to enable their features.
+ await connection.registerFeatureNegotiators([
+ // This negotiator authenticates to the server using SASL PLAIN with the provided
+ // credentials.
+ SaslPlainNegotiator(),
+ // This negotiator attempts to bind a resource. By default, it's always a random one.
+ ResourceBindingNegotiator(),
+ // This negotiator attempts to do StartTLS before authenticating.
+ StartTlsNegotiator(),
+ ]);
+
+ // Set up a stream handler for the connection's event stream. Managers and negotiators
+ // may trigger certain events. The [MessageManager], for example, triggers a [MessageEvent]
+ // whenever a message is received. If other managers are registered that parse a message's
+ // contents, then they can add their data to the event.
+ connection.asBroadcastStream().listen((event) {
+ if (event is! MessageEvent) {
+ return;
+ }
+
+ // The text body (contents of the element) are returned as a
+ // [MessageBodyData] object. However, a message does not have to contain a
+ // body, so it is nullable.
+ final body = event.extensions.get()?.body;
+ print('[<-- ${event.from}] $body');
+ });
+
+ // Connect to the server.
+ final result = await connection.connect(
+ // This flag indicates that we want to reconnect in case something happens.
+ shouldReconnect: true,
+ // This flag indicates that we want the returned Future to only resolve
+ // once the stream negotiations are done and no negotiator has any feature left
+ // to negotiate.
+ waitUntilLogin: true,
+ );
+
+ // Check if the connection was successful. [connection.connect] can return a boolean
+ // to indicate success or a [XmppError] in case the connection attempt failed.
+ if (!result.isType()) {
+ print('Failed to connect to server');
+ return;
+ }
+}