fourb_mdns 0.5.0 copy "fourb_mdns: ^0.5.0" to clipboard
fourb_mdns: ^0.5.0 copied to clipboard

Let people pick a server off the local network from inside your Flutter app, and remember which one they chose. Scans mDNS/Bonjour natively — no server.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:fourb_mdns/fourb_mdns.dart';

void main() => runApp(const ExampleApp());

class ExampleApp extends StatefulWidget {
  const ExampleApp({super.key});

  @override
  State<ExampleApp> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  bool _httpOnly = true;
  bool _launcher = true;

  @override
  Widget build(BuildContext context) {
    return MdnsScope(
      httpOnly: _httpOnly,
      launcher: _launcher,
      onSelect: (s) => debugPrint('[mdns] selected: ${s?.address}'),
      child: MaterialApp(
        title: 'fourb_mdns',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          colorSchemeSeed: const Color(0xFF4F8CFF),
          useMaterial3: true,
        ),
        home: HomePage(
          httpOnly: _httpOnly,
          launcher: _launcher,
          onHttpOnly: (v) => setState(() => _httpOnly = v),
          onLauncher: (v) => setState(() => _launcher = v),
        ),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({
    super.key,
    required this.httpOnly,
    required this.launcher,
    required this.onHttpOnly,
    required this.onLauncher,
  });

  final bool httpOnly;
  final bool launcher;
  final ValueChanged<bool> onHttpOnly;
  final ValueChanged<bool> onLauncher;

  @override
  Widget build(BuildContext context) {
    final mdns = MdnsScope.of(context);
    final theme = Theme.of(context);

    return Scaffold(
      appBar: AppBar(
        title: const Text('fourb_mdns'),
        actions: [
          Padding(
            padding: const EdgeInsets.only(right: 16),
            child: Center(
              child: Text(
                mdns.isScanning ? 'scanning…' : '${mdns.allServices.length} found',
                style: theme.textTheme.bodySmall,
              ),
            ),
          ),
        ],
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('Selection', style: theme.textTheme.titleSmall),
                  const SizedBox(height: 8),
                  if (!mdns.isRestored)
                    const Text('reading saved pick…')
                  else if (mdns.selection == null)
                    const Text('Nothing selected yet.')
                  else ...[
                    _kv(context, 'name', mdns.selection!.name),
                    _kv(context, 'address', mdns.address ?? '—'),
                    _kv(context, 'ip', mdns.ip ?? '—'),
                    _kv(context, 'port', '${mdns.selection!.port}'),
                    _kv(context, 'url', mdns.url ?? '—'),
                  ],
                  const SizedBox(height: 12),
                  Wrap(
                    spacing: 8,
                    children: [
                      FilledButton.tonal(
                        onPressed: () => showMdnsPicker(context),
                        child: const Text('Open picker'),
                      ),
                      OutlinedButton(
                        onPressed: mdns.isScanning ? null : () => mdns.scan(),
                        child: const Text('Rescan'),
                      ),
                      OutlinedButton(
                        onPressed: mdns.selection == null ? null : () => mdns.clear(),
                        child: const Text('Clear'),
                      ),
                    ],
                  ),
                  if (mdns.error != null) ...[
                    const SizedBox(height: 8),
                    Text('${mdns.error}',
                        style: TextStyle(color: theme.colorScheme.error)),
                  ],
                ],
              ),
            ),
          ),
          const SizedBox(height: 12),
          Card(
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  SwitchListTile(
                    contentPadding: EdgeInsets.zero,
                    title: const Text('httpOnly'),
                    subtitle: const Text('_http._tcp / _https._tcp only'),
                    value: httpOnly,
                    onChanged: onHttpOnly,
                  ),
                  SwitchListTile(
                    contentPadding: EdgeInsets.zero,
                    title: const Text('launcher'),
                    subtitle: const Text('built-in floating button'),
                    value: launcher,
                    onChanged: onLauncher,
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 12),
          Text('Scanned network', style: theme.textTheme.titleSmall),
          const SizedBox(height: 8),
          if (mdns.services.isEmpty)
            const Padding(
              padding: EdgeInsets.all(24),
              child: Center(child: Text('nothing yet')),
            )
          else
            ...mdns.services.map(
              (s) => Card(
                margin: const EdgeInsets.only(bottom: 6),
                child: ListTile(
                  dense: true,
                  selected: mdns.selection?.id == s.id,
                  title: Text(s.name),
                  subtitle: Text(
                    '${s.kind} · ${s.host}:${s.port} · ${s.ip ?? "—"}',
                    style: const TextStyle(fontFamily: 'monospace', fontSize: 11),
                  ),
                  trailing:
                      mdns.selection?.id == s.id ? const Icon(Icons.check) : null,
                  onTap: () => mdns.select(s),
                ),
              ),
            ),
          const SizedBox(height: 80),
        ],
      ),
    );
  }

  Widget _kv(BuildContext context, String k, String v) => Padding(
        padding: const EdgeInsets.symmetric(vertical: 2),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            SizedBox(
              width: 80,
              child: Text(k, style: Theme.of(context).textTheme.bodySmall),
            ),
            Expanded(
              child: SelectableText(
                v,
                style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
              ),
            ),
          ],
        ),
      );
}
1
likes
160
points
273
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Let people pick a server off the local network from inside your Flutter app, and remember which one they chose. Scans mDNS/Bonjour natively — no server.

Repository
View/report issues

Topics

#mdns #bonjour #discovery #networking

License

MIT (license)

Dependencies

flutter, meta, multicast_dns, nsd, shared_preferences, url_launcher

More

Packages that depend on fourb_mdns