dart_meteor 패키지는 Flutter 앱과 Meteor 백엔드 간의 원활한 통합을 가능하게 합니다. StreamBuilder와 FutureBuilder를 활용하여 실시간 데이터 동기화를 단순화하여 라이브 업데이트 및 비동기 작업 처리를 쉽게 할 수 있습니다. Meteor를 백엔드로 사용하는 개발자를 위해 설계되었으며, 복잡한 네트워크 로직을 추상화하여 Meteor의 DDP(Distributed Data Protocol)와 원활한 통신을 가능하게 합니다. Android, iOS, macOS, Windows, Linux 등 여러 플랫폼을 지원하여 크로스플랫폼 일관성을 보장합니다. 최소한의 보일러플레이트로 반응형 실시간 애플리케이션을 구축하기에 이상적입니다.
사용 사례
Flutter 앱에서 실시간 데이터 동기화
Flutter 프론트엔드를 Meteor 백엔드에 연결하는 방법
실시간 대시보드 또는 채팅 애플리케이션 구축
장치 간 데이터 동기화
기존 메테오르 기반 서비스와의 통합
주요 기능
StreamBuilder 및 FutureBuilder와의 원활한 통합
Meteor의 DDP 프로토콜 지원
크로스 플랫폼 호환성
실시간 업데이트를 위한 최소 설정
내장된 오류 처리 및 재연결 로직
적합한 대상
백엔드로 메테오르를 사용하는 개발자
실시간 데이터 동기화가 필요한 앱
저지연 업데이트가 필요한 Flutter 프로젝트
메테오 생태계에 익숙한 팀
반응형 UI의 빠른 개발을 우선시하는 프로젝트
고려 사항
실행 중인 메테오 서버가 필요합니다
주류 패키지에 비해 문서가 제한적
복잡한 인증을 위해 추가 구성이 필요할 수 있습니다
비-Meteor 백엔드에는 적합하지 않습니다
제3자에서 유지 관리하는 Dart DDP 클라이언트에 의존
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
dart_meteor — a Meteor DDP client for Dart/Flutter
Connect your Flutter app to a Meteor backend over DDP.
Designed to work seamlessly with StreamBuilder and FutureBuilder.
Platforms: Dart VM, Flutter iOS/Android/Web
Dart: 3.6 or newer
Meteor: compatible with Meteor 2.x and 3.x servers (tested against Meteor 3.5.1)
Features
Method calls with Future-based results
Subscriptions and reactive collections as Streams
Accounts: login with password/token, logout, password management
Automatic reconnection with backoff, re-login and re-subscription
App lifecycle aware: detects a connection that died while the device slept
DateTime values are converted to/from EJSON $date automatically
Installation
Add the package to your pubspec.yaml:
dependencies:dart_meteor:^4.1.0
Quick start
Create a single MeteorClient instance in your app's global scope so you can
use it anywhere in your project. The client connects immediately and keeps the
connection alive:
import'package:flutter/material.dart';
import'package:dart_meteor/dart_meteor.dart';
final meteor = MeteorClient.connect(url: 'https://yourdomain.com');
voidmain() => runApp(MyApp());
The url may be https://… or wss://…; the client appends the /websocket
DDP endpoint for you.
Then build widgets from the client's streams:
classMyAppextendsStatelessWidget{
constMyApp({super.key});
@overrideWidgetbuild(BuildContext context) {
returnMaterialApp(
home: Scaffold(
appBar: AppBar(title: constText('dart_meteor example')),
body: Column(
children: [
// Show the live connection status.StreamBuilder<DdpConnectionStatus>(
stream: meteor.status(),
builder: (context, snapshot) {
if (!snapshot.hasData) returnconstText('Status: ---');
returnText('Status: ${snapshot.data}');
},
),
// Show a login/logout button depending on the current user.StreamBuilder<String?>(
stream: meteor.userId(),
builder: (context, snapshot) {
if (snapshot.data != null) {
returnElevatedButton(
onPressed: () => meteor.logout(),
child: constText('Logout'),
);
}
returnElevatedButton(
onPressed: () =>
meteor.loginWithPassword('username', 'password'),
child: constText('Login'),
);
},
),
],
),
),
);
}
}
A complete runnable app is in /example: a Flutter chat client
(iOS, Android and Web) that connects to the live demo server at
https://simple-meteor-chat.tanutapi.dev and exercises login, subscriptions,
collections and method calls. There is also a longer walk-through covering
connection status, authentication, and subscriptions in this Medium post.
Method calls
meteor.call() returns a Future. Always handle errors — an unhandled
MeteorError will otherwise crash your app:
try {
final result = await meteor.call('sumMethod', args: [5, 10]);
print('Answer is $result'); // 15
} onMeteorErrorcatch (err) {
print('${err.error}: ${err.reason}');
}
Arguments are optional — meteor.call('helloMethod') works too. A DateTime
anywhere in the arguments or the result is converted to/from Meteor's EJSON
date format automatically.
Method calls also fit naturally into a FutureBuilder:
FutureBuilder<dynamic>(
future: meteor.call('sumMethod', args: [5, 10]),
builder: (context, snapshot) {
if (snapshot.hasError) returnText('Error: ${snapshot.error}');
if (!snapshot.hasData) returnconstCircularProgressIndicator();
returnText('Answer is: ${snapshot.data}');
},
),
Subscriptions and collections
Subscribe to a publication on the server, and read the documents it publishes
through meteor.collection():
meteor.subscribe() returns a SubscriptionHandler with stop() and a
ready() stream that emits true once the server has sent the initial
batch of documents. Optional onReady and onStop callbacks are also
supported. Subscriptions are re-established automatically after a reconnect.
meteor.collection() returns a stream backed by an rxdart
BehaviorSubject: every new listener immediately receives the latest value,
so a StreamBuilder starts with snapshot.hasData == true and an empty map
before any documents arrive.
The emitted value is a Map<String, dynamic> keyed by document _id, with
the whole document as the value:
{"DGbsysgxzSf7Cr8Jg":{"_id":"DGbsysgxzSf7Cr8Jg","field1":0,"field2":"a","field3":true,"field4":"2020-08-30T16:15:57.000Z"// delivered as a Dart DateTime}}
There is no minimongo on the client. Use plain Dart collection operations
(where, map, reduce, …) to query the map — they cover the same ground as
minimongo queries in the Meteor web client.
Looking up a document by id
Since the collection is a map keyed by _id, a lookup is just an index
operation:
// Non-reactive read of a document by its id.final doc = meteor.collectionCurrentValue('your_collection_name')?['DGbsysgxzSf7Cr8Jg'];
if (doc != null) {
// do something
}
// The same works for users.final user = meteor.collectionCurrentValue('users')?['Sf7Cr8JgDGbsysgxz'];
Reading current values without a stream
When you only need the latest value for a condition check — not a reactive
rebuild — every major stream has a non-reactive counterpart:
Reactive stream
Current value
meteor.collection(name)
meteor.collectionCurrentValue(name)
meteor.user()
meteor.userCurrentValue()
meteor.userId()
meteor.userIdCurrentValue()
Accounts
// Log in (works with a username or an email address; the password is sent// as a SHA-256 digest, never in plain text).final result = await meteor.loginWithPassword('user_or_email', 'password');
// Resume a session with a saved token, e.g. after an app restart.await meteor.loginWithToken(token: result.token, tokenExpires: result.tokenExpires);
// Log out.await meteor.logout();
Related APIs: meteor.user(), meteor.userId(), meteor.loggingIn(), and
meteor.logInStatus() are reactive streams of the current account state;
logoutOtherClients(), changePassword(), forgotPassword(), and
resetPassword() cover the rest of the standard accounts flows. After a
reconnect the client re-authenticates automatically using its stored token.
Connection management
meteor.status(); // Stream<DdpConnectionStatus>: connected/connecting/failed/waiting/offline
meteor.reconnect(); // force a reconnection attempt if not connected
meteor.disconnect(); // close the connection and stop reconnecting
While connected, the client exchanges DDP ping/pong with the server and
reconnects when the connection is considered dead, backing off between
attempts (0s, 5s, 10s, … up to maxRetryInterval) so an unreachable server
does not keep the radio busy. disconnect() is final: the client stays offline
until you call reconnect().
The timings are configurable if the defaults do not suit your server:
When a phone sleeps, the OS suspends the process: Dart timers stop firing, and
the server can drop the session without the socket ever reporting an error. The
app then wakes up believing it is still connected, and stays that way until the
next ping happens to time out.
dart_meteor is a pure Dart package, so it does not watch Flutter's lifecycle
itself. Forward it from a WidgetsBindingObserver — this is the whole
integration:
On resume the client measures by wall clock how long it was actually away
rather than trusting its timers. If the connection has been silent longer than
stalenessThreshold it is torn down and replaced immediately, re-resuming the
login and re-subscribing. While paused, the client will not tear down a
connection just because a timer fired late.
meteor.checkLiveness() runs the same check on demand — useful if your app
learns from somewhere else (a connectivity plugin, say) that the network may
have changed.
Server-side Meteor.Errors are thrown as MeteorError, which exposes
error, reason, message, details, errorType, and isClientSafe — the
same fields you get in a Meteor web client.
A call that was still in flight when the connection dropped — because the
device slept, or the network went away — throws MeteorConnectionError
instead. The two are worth distinguishing: MeteorError means the server
considered the request and said no, while MeteorConnectionError means you
never heard back and the method may or may not have run.
try {
await meteor.call('sendMessage', args: ['hello']);
} onMeteorErrorcatch (err) {
// The server rejected it.
} onMeteorConnectionErrorcatch (err) {
// Never got a reply — offer a retry.
}
Calls are not resent automatically after a reconnect: a method like
sendMessage is not safe to run twice, so whether to retry is left to you.
Upgrading
See CHANGELOG.md for the full history. The notable breaking
changes:
4.1.0 — two behaviour changes worth knowing about, both fixes. A method
call that is in flight when the connection drops now throws
MeteorConnectionError instead of hanging forever, so await meteor.call(…)
can now throw where it previously never returned. And reconnect attempts now
back off instead of retrying immediately.
4.0.0 — requires Dart 3.6+; verified against Meteor 3.x (incl. 3.5.1);
web support via web_socket_channel. The DdpClient.PING_SEC_INTERVAL and
DdpClient.PONG_WITHIN_SEC fields were renamed to the static constants
DdpClient.pingIntervalSeconds and DdpClient.pongTimeoutSeconds.
3.0.0 — meteor.collection() streams start with snapshot.hasData == true and an empty map instead of no data.
2.0.0 — method/subscription arguments became a named parameter:
meteor.call('method', args: [...]), meteor.subscribe('pub', args: [...])
(both optional). prepareCollection() is no longer needed — just call
meteor.collection(). DateTime values are supported directly.
Features and bugs
Please file feature requests and bugs at the issue tracker.