Experiment with advertising a ZeroConf Service

This commit is contained in:
Vishesh Handa
2020-09-27 22:46:18 +02:00
parent 58de0498ac
commit fc477bd503
4 changed files with 282 additions and 4 deletions

View File

@ -5,6 +5,7 @@ Future<void> main() async {
var name = '_dartobservatory._tcp.local';
name = '_gitjournal._tcp';
name = '_bonsoirdemo._tcp';
final MDnsClient client = MDnsClient();
// Start the client with default options.
await client.start();
@ -39,5 +40,9 @@ Future<void> main() async {
// Use connectivity plugin to get ssid info when connected to the wifi network
// https://stackoverflow.com/questions/55716751/flutter-ios-reading-wifi-name-using-the-connectivity-or-wifi-plugin/55732656#55732656
// For Registering on Mobiles -
// https://pub.dev/packages/bonsoir
// Use the WorkManager in Android to do the syncing in the background
// -> This should be configurable if we want to be able to sync in the background
// TODO:
// * I don't know if/how I can run an HTTP Server in the Background
// Is that still possible with current Android Versions?

258
lib/main_zeroconf.dart Normal file
View File

@ -0,0 +1,258 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:bonsoir/bonsoir.dart';
import 'package:device_info/device_info.dart';
import 'package:provider/provider.dart';
/// Plugin's main method.
void main() => runApp(BonsoirExampleMainWidget());
/// Allows to get the Bonsoir service corresponding to the current device.
class AppService {
/// The service type.
static const String type = '_bonsoirdemo._tcp';
/// The service port (in this example we're not doing anything on that port but you should).
static const int port = 4000;
/// The cached service.
static BonsoirService _service;
/// Returns (and create if needed) the app Bonsoir service.
static Future<BonsoirService> getService() async {
if (_service != null) {
return _service;
}
String name;
if (Platform.isAndroid) {
name = (await DeviceInfoPlugin().androidInfo).model;
} else if (Platform.isIOS) {
name = (await DeviceInfoPlugin().iosInfo).localizedModel;
} else {
name = 'Flutter';
}
name += ' Bonsoir Demo';
_service = BonsoirService(name: name, type: type, port: port);
return _service;
}
}
/// Provider model that allows to handle Bonsoir broadcasts.
class BonsoirBroadcastModel extends ChangeNotifier {
/// The current Bonsoir broadcast object instance.
BonsoirBroadcast _bonsoirBroadcast;
/// Whether Bonsoir is currently broadcasting the app's service.
bool _isBroadcasting = false;
/// Returns wether Bonsoir is currently broadcasting the app's service.
bool get isBroadcasting => _isBroadcasting;
/// Starts the Bonsoir broadcast.
Future<void> start({bool notify = true}) async {
if (_bonsoirBroadcast == null || _bonsoirBroadcast.isStopped) {
_bonsoirBroadcast =
BonsoirBroadcast(service: await AppService.getService());
await _bonsoirBroadcast.ready;
}
await _bonsoirBroadcast.start();
_isBroadcasting = true;
if (notify) {
notifyListeners();
}
}
/// Stops the Bonsoir broadcast.
void stop({bool notify = true}) {
_bonsoirBroadcast?.stop();
_isBroadcasting = false;
if (notify) {
notifyListeners();
}
}
@override
void dispose() {
stop(notify: false);
super.dispose();
}
}
/// Provider model that allows to handle Bonsoir discoveries.
class BonsoirDiscoveryModel extends ChangeNotifier {
/// The current Bonsoir discovery object instance.
BonsoirDiscovery _bonsoirDiscovery;
/// Contains all discovered (and resolved) services.
final List<ResolvedBonsoirService> _resolvedServices = [];
/// The subscription object.
StreamSubscription<BonsoirDiscoveryEvent> _subscription;
/// Creates a new Bonsoir discovery model instance.
BonsoirDiscoveryModel() {
start();
}
/// Returns all discovered (and resolved) services.
List<ResolvedBonsoirService> get discoveredServices =>
List.of(_resolvedServices);
/// Starts the Bonsoir discovery.
Future<void> start() async {
if (_bonsoirDiscovery == null || _bonsoirDiscovery.isStopped) {
_bonsoirDiscovery =
BonsoirDiscovery(type: (await AppService.getService()).type);
await _bonsoirDiscovery.ready;
}
await _bonsoirDiscovery.start();
_subscription = _bonsoirDiscovery.eventStream.listen(_onEventOccurred);
}
/// Stops the Bonsoir discovery.
void stop() {
_subscription?.cancel();
_subscription = null;
_bonsoirDiscovery?.stop();
}
/// Triggered when a Bonsoir discovery event occurred.
void _onEventOccurred(BonsoirDiscoveryEvent event) {
if (event.service == null || !event.isServiceResolved) {
return;
}
if (event.type == BonsoirDiscoveryEventType.DISCOVERY_SERVICE_RESOLVED) {
_resolvedServices.add(event.service);
notifyListeners();
} else if (event.type == BonsoirDiscoveryEventType.DISCOVERY_SERVICE_LOST) {
_resolvedServices.remove(event.service);
notifyListeners();
}
}
@override
void dispose() {
stop();
super.dispose();
}
}
/// Allows to switch the app broadcast state.
class BroadcastSwitch extends StatelessWidget {
@override
Widget build(BuildContext context) {
BonsoirBroadcastModel model = context.watch<BonsoirBroadcastModel>();
return InkWell(
onTap: () => _onTap(model),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('Broadcast'.toUpperCase()),
Switch(
value: model.isBroadcasting,
onChanged: (value) => _onTap(model),
activeColor: Colors.white,
activeTrackColor: Colors.white54,
),
],
),
);
}
/// Triggered when the widget has been tapped on.
void _onTap(BonsoirBroadcastModel model) {
if (model.isBroadcasting) {
model.stop();
} else {
model.start();
}
}
}
/// Allows to display all discovered services.
class ServiceList extends StatelessWidget {
@override
Widget build(BuildContext context) {
BonsoirDiscoveryModel model = context.watch<BonsoirDiscoveryModel>();
List<ResolvedBonsoirService> discoveredServices = model.discoveredServices;
if (discoveredServices.isEmpty) {
return const Padding(
padding: EdgeInsets.all(20),
child: Center(
child: Text(
'Found no service of type "${AppService.type}".',
style: TextStyle(
color: Colors.black54,
fontStyle: FontStyle.italic,
),
),
),
);
}
return ListView.builder(
itemCount: discoveredServices.length,
itemBuilder: (context, index) =>
_ServiceWidget(service: discoveredServices[index]),
);
}
}
/// Allows to display a discovered service.
class _ServiceWidget extends StatelessWidget {
/// The discovered service.
final ResolvedBonsoirService service;
/// Creates a new service widget.
const _ServiceWidget({
@required this.service,
});
@override
Widget build(BuildContext context) => ListTile(
title: Text(service.name),
subtitle: Text(
'Type : ${service.type}, ip : ${service.ip}, port : ${service.port}'),
);
}
/// Allows to display the app title based on how many services have been discovered.
class TitleWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
int count =
context.watch<BonsoirDiscoveryModel>().discoveredServices.length;
return Text(count == 0 ? 'Bonsoir app demo' : 'Found $count service(s)');
}
}
/// The main widget.
class BonsoirExampleMainWidget extends StatelessWidget {
@override
Widget build(BuildContext context) => MultiProvider(
providers: [
ChangeNotifierProvider<BonsoirBroadcastModel>(
create: (context) => BonsoirBroadcastModel()),
ChangeNotifierProvider<BonsoirDiscoveryModel>(
create: (context) => BonsoirDiscoveryModel()),
],
builder: (context, child) => MaterialApp(
home: Scaffold(
appBar: AppBar(
title: TitleWidget(),
actions: [BroadcastSwitch()],
centerTitle: false,
),
body: ServiceList(),
),
),
);
}

View File

@ -64,6 +64,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
bonsoir:
dependency: "direct main"
description:
name: bonsoir
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2+2"
boolean_selector:
dependency: transitive
description:
@ -588,6 +595,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
nested:
dependency: transitive
description:
name: nested
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4"
node_interop:
dependency: transitive
description:
@ -713,7 +727,7 @@ packages:
name: provider
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.0"
version: "4.3.2+2"
pub_cache:
dependency: transitive
description:

View File

@ -28,7 +28,7 @@ dependencies:
fimber: ^0.3.0
dynamic_theme: ^1.0.0
flutter_staggered_grid_view: ^0.3.0
provider: ^3.2.0
provider: ^4.3.2+2
git_bindings: ^0.0.15
dart_git:
git: https://github.com/GitJournal/dart_git.git
@ -57,6 +57,7 @@ dependencies:
flutter_plugin_android_lifecycle: ^1.0.8 # for fixing the build
timeago: ^2.0.27
multicast_dns: ^0.2.2
bonsoir: ^0.1.2+2
dev_dependencies:
flutter_launcher_icons: "^0.7.2"