# Flutter SDK

> Flutter client for Aerostack -- authentication, realtime, storage, and more for iOS, Android, web, and desktop.

The Flutter SDK is generated from the OpenAPI spec and focuses on client-side features: auth, realtime, and storage. For database, cache, and queue operations, call your server-side Logic Modules via the API.

  **Beta** -- The Flutter SDK is in active beta. APIs may change between minor versions.

## What you can build

- **Mobile apps with auth** -- Registration, login, OTP, password reset on iOS and Android
- **Real-time chat** -- Live messaging with presence indicators and message history
- **Collaborative features** -- Shared lists, collaborative editing, live cursors
- **Photo/file uploads** -- Camera capture to CDN with progress tracking
- **Live dashboards** -- Real-time data feeds on mobile and tablet
- **Cross-platform apps** -- Single codebase for iOS, Android, web, and desktop

## Install

Add to `pubspec.yaml`:

```yaml
dependencies:
  aerostack: ^1.0.0
```

```bash
flutter pub get
```

## Initialize

```dart

final client = AerostackClient(
  projectId: 'your-project-id',
  apiKey: 'your-public-api-key',
  baseUrl: 'https://api.aerostack.dev/v1',
);
```

## Auth

Full authentication lifecycle -- registration, login, OTP, password reset, and email verification:

```dart
// Register
final result = await client.auth.register(
  email: 'jane@example.com',
  password: 'securePassword123',
  name: 'Jane Doe',
);

if (result.requiresVerification) {
  // Show "check your email" screen
}

// Login
final result = await client.auth.login(
  email: 'jane@example.com',
  password: 'securePassword123',
);
final accessToken = result.accessToken;

// Passwordless OTP
await client.auth.sendOtp(email: 'jane@example.com');
final result = await client.auth.verifyOtp(
  email: 'jane@example.com',
  code: '482916',
);

// Get current user
final user = await client.auth.me(accessToken: accessToken);

// Password reset
await client.auth.requestPasswordReset(email: 'jane@example.com');
await client.auth.resetPassword(token: resetToken, newPassword: 'newPassword456');

// Email verification
await client.auth.verifyEmail(token: verificationToken);

// Logout
await client.auth.logout(accessToken: accessToken, refreshToken: refreshToken);
```

### AuthResult type

```dart
class AuthResult {
  final String? accessToken;
  final String? refreshToken;
  final int? expiresAt;
  final User? user;
  final bool requiresVerification;
}

class User {
  final String id;
  final String email;
  final String? name;
  final String? avatarUrl;
  final bool emailVerified;
  final Map? customFields;
}
```

## Realtime

Pub/sub channels with presence tracking and message history:

```dart
final channel = client.realtime.channel('chat/general');

// Listen for messages
channel.on('message', (payload) {
  print('New message: ${payload.data}');
  setState(() {
    messages.add(payload.data);
  });
});

// Subscribe to DB changes
final ordersChannel = client.realtime.channel('orders');
ordersChannel.on('INSERT', (payload) {
  print('New order: ${payload.data}');
});

await channel.subscribe();
await ordersChannel.subscribe();

// Publish a message
channel.publish('message', {
  'text': 'Hello from Flutter!',
  'userId': currentUser.id,
  'timestamp': DateTime.now().millisecondsSinceEpoch,
});

// Publish with persistence (for message history)
channel.publish('message', messageData, persist: true);

// Presence -- track who's online
channel.track({
  'userId': currentUser.id,
  'name': currentUser.name,
  'status': 'online',
});

channel.on('presence:join', (payload) {
  print('${payload.data['name']} joined');
});

channel.on('presence:leave', (payload) {
  print('${payload.data['name']} left');
});

// Message history
final messages = await channel.getHistory(limit: 50);

// Cleanup
channel.untrack();
channel.unsubscribe();
```

## Storage

Upload files from the device:

```dart

final file = File('/path/to/photo.jpg');

final result = await client.storage.upload(
  file: file,
  path: 'avatars/${currentUser.id}.jpg',
  contentType: 'image/jpeg',
);

print(result.url); // CDN URL for the uploaded file
```

## Complete example: chat screen

```dart title="lib/screens/chat_screen.dart"

class ChatScreen extends StatefulWidget {
  final String roomId;
  const ChatScreen({required this.roomId});

  @override
  State createState() => _ChatScreenState();
}

class _ChatScreenState extends State {
  final _controller = TextEditingController();
  final _messages = >[];
  final _online = >{};
  late final channel;

  @override
  void initState() {
    super.initState();
    _initRealtime();
  }

  Future<void> _initRealtime() async {
    channel = client.realtime.channel('chat/${widget.roomId}');

    // Load history
    final history = await channel.getHistory(limit: 100);
    setState(() {
      _messages.addAll(history.map((h) => h.data));
    });

    // Track presence
    channel.track({
      'userId': currentUser.id,
      'name': currentUser.name,
    });

    // Listen for new messages
    channel.on('message', (payload) {
      setState(() {
        _messages.add(payload.data);
      });
    });

    // Listen for presence changes
    channel.on('presence:join', (payload) {
      setState(() {
        _online[payload.data['userId']] = payload.data;
      });
    });

    channel.on('presence:leave', (payload) {
      setState(() {
        _online.remove(payload.data['userId']);
      });
    });

    await channel.subscribe();
  }

  void _sendMessage() {
    if (_controller.text.isEmpty) return;

    channel.publish('message', {
      'userId': currentUser.id,
      'name': currentUser.name,
      'text': _controller.text,
      'timestamp': DateTime.now().millisecondsSinceEpoch,
    }, persist: true);

    _controller.clear();
  }

  @override
  void dispose() {
    channel.untrack();
    channel.unsubscribe();
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Chat (${_online.length} online)'),
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: _messages.length,
              itemBuilder: (ctx, i) {
                final msg = _messages[i];
                return ListTile(
                  title: Text(msg['name'] ?? 'Unknown'),
                  subtitle: Text(msg['text'] ?? ''),
                );
              },
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(hintText: 'Message...'),
                  ),
                ),
                IconButton(
                  icon: const Icon(Icons.send),
                  onPressed: _sendMessage,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
```

## Next steps

- [API Reference](/sdk/flutter/api-reference) -- Full method listing
- [Auth](/sdk/auth) -- Detailed authentication patterns
- [Realtime](/sdk/realtime) -- Full guide for pub/sub, presence, and DB change events
- [Error Handling](/sdk/error-handling) -- Error types and retry strategies
