Skip to content

Commit

Permalink
Watch app can now send data to Flutter app
Browse files Browse the repository at this point in the history
  • Loading branch information
SpiralArm Consulting Ltd committed Jan 29, 2019
1 parent 87c8d52 commit f8b4d42
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 3 deletions.
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,22 @@ My plan is to:
- *1.1* Add a blank watch project and get it working
- *1.2* Modify the watch app functionally into WatchTips
- *1.3* Update the Flutter app to WatchTips
- *1.4* **See if I can bridge between the 2 app (as the Titanium version does)**

- *1.4* See if I can bridge between the 2 app (as the Titanium version does)
- **1.5 Tidy up both the Flutter code and the iOS project and use it to submit the app into the App Store**

## Versions

### 1.4 - Rough and Ready but Working!
I have now managed to add the required code into the iOS project to allow it to communicate with Flutter and send data updates from the watch so they are reflected in the app.
Basically if you do a calculation for a Tip on the watch then the data is transferred to the associated device.

You need to forgive the (bad?) Objective C coding as it is not really my thing - who in their right mind thought of it in the first place anyway :-) . A lot of this was just brute force trial and error as well as a lot of searching and testing.

Apart from that the difficult issue for me was that although **MethodChannel** is quite well documented, **EventChannel** is not, the only examples I found were to do with Plugin extensions, but again probably due to my dislike (due to not understanding it very well) of Objective C.

I will try to create a generic plug-in later, but that will be done in Swift.


### 1.3
So after a bit of a hair pulling out session I have managed to get this working. I now have a iWatch app as well as a Flutter iOS app to build and run. I have tested this by building and distribuing to a physical phone via HockeyApp. Now the bad news......

Expand Down
10 changes: 10 additions & 0 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,20 @@
CreatedOnToolsVersion = 10.1;
DevelopmentTeam = QJ23459J94;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.BackgroundModes.watchos.extension = {
enabled = 0;
};
};
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = QJ23459J94;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
};
};
};
};
};
Expand Down
11 changes: 10 additions & 1 deletion ios/Runner/AppDelegate.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import <WatchConnectivity/WatchConnectivity.h>

@interface AppDelegate : FlutterAppDelegate

@interface WTUserInfoHandler : NSObject <FlutterStreamHandler>
@end

// Modified to add the Watch Session Delegate
@interface AppDelegate : FlutterAppDelegate <WCSessionDelegate>{
@private
WCSession *watchSession;
WTUserInfoHandler* userInfoStreamHandler;
}
@end
105 changes: 105 additions & 0 deletions ios/Runner/AppDelegate.m
Original file line number Diff line number Diff line change
@@ -1,11 +1,116 @@
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"


@implementation WTUserInfoHandler

FlutterEventSink sink;
NSDictionary *info;

- (void) updateUserInfo:(nonnull NSDictionary<NSString *, id> *)userInfo{
NSLog(@"Update Recieved");
if(info != userInfo){
info = userInfo;
if(sink!=nil){
sink(@[userInfo]);
}
}
}

- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink {
NSLog(@"Adding Flutter Listener");
sink = eventSink;
return nil;
}

- (FlutterError*)onCancelWithArguments:(id)arguments {
NSLog(@"Removing Flutter Listener");
if(sink!=nil){
sink = nil;
}
return nil;
}

@end


@implementation AppDelegate

#pragma mark watch session delegates

// Event triggered when userInfo Rxd
- (void)session:(nonnull WCSession *)session didReceiveUserInfo:(nonnull NSDictionary<NSString *, id> *)userInfo
{
if(userInfoStreamHandler != nil){
[userInfoStreamHandler updateUserInfo:userInfo];
}
}




#pragma mark watch session methods

// Method used to enable the Watch session
- (void)activateSession
{
[self activateChannel];
[self watchSession];
}

// create our eventChannel
-(FlutterEventChannel *) activateChannel {
NSLog(@"activateChannel");
userInfoStreamHandler = [[WTUserInfoHandler alloc] init];
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:@"uk.spiralarm.watchtips/tipinfo/watchdata" binaryMessenger:controller];
[eventChannel setStreamHandler:(NSObject<FlutterStreamHandler> * _Nullable) userInfoStreamHandler ];
return eventChannel;
}


// activate session
- (WCSession *)watchSession
{
if (watchSession == nil) {
if ([WCSession isSupported]) {
watchSession = [WCSession defaultSession];
[watchSession setDelegate:self];
[watchSession activateSession];
} else {
NSLog(@"Error - Watch Connectivity NOT supported");
}
}
return watchSession;
}
- (void)dealloc
{
if (watchSession != nil) {
[watchSession setDelegate:nil];
}
}

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel *tipinfoChannel = [FlutterMethodChannel
methodChannelWithName:@"uk.spiralarm.watchtips/tipinfo"
binaryMessenger:controller];

//__weak typeof(self) weakSelf = self;
[tipinfoChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {

// activate Session
if([@"activateSession" isEqualToString:call.method]){
[self activateSession];
result(@"WatchTips Activated");
}

}];

[GeneratedPluginRegistrant registerWithRegistry:self];

// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
Expand Down
5 changes: 5 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand Down
47 changes: 47 additions & 0 deletions lib/homescreen.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';

class HomeScreen extends StatefulWidget {
Expand All @@ -8,12 +10,35 @@ class HomeScreen extends StatefulWidget {

class _HomeScreenState extends State<HomeScreen> {

static const platform = const MethodChannel('uk.spiralarm.watchtips/tipinfo');
static const stream = const EventChannel('uk.spiralarm.watchtips/tipinfo/watchdata');
StreamSubscription tipSubscription;

final formatter = new NumberFormat("##0.00");
TextEditingController billTotalController = TextEditingController(text: "0.00");
int tipPercent = 10, tipSplit = 1;
double billTotal = 0.0;
double totalWithTip = 0.0;
double totalEach = 0.0;
String buttonName = "Activate";


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


@override
void dispose() {
if(tipSubscription!=null){
tipSubscription.cancel();
tipSubscription = null;
}
super.dispose();
}


@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -159,9 +184,31 @@ class _HomeScreenState extends State<HomeScreen> {
total = (total ?? billTotal);
setState(() {
billTotal = total;
billTotalController.text = "${formatter.format(billTotal)}";
double tip = (total/100) * tipPercent;
totalWithTip = total +tip;
totalEach = (totalWithTip/tipSplit);
});
}

/// Active and start the connection to the Watch App
activateWatchConnection() async {

// Start initial Session to allow watch an iOS to swap user data
await platform.invokeMethod("activateSession");

// Connect up our stream so we can monitor for watch updates
stream.receiveBroadcastStream().listen((value){
List result = value;
debugPrint("${result[0]}");
if(result[0]!=null){
tipPercent = int.tryParse(result[0]['tip']);
tipSplit = int.tryParse(result[0]['split']);
billTotal = double.tryParse(result[0]['bill']);
calculateBill(null);
}
});

}

}

0 comments on commit f8b4d42

Please sign in to comment.