How to call native iOS function from Flutter using the Pigeon package? - android

I am trying to integrate some native code in my project. For that, I am using the new Pigeon package: https://pub.dev/packages/pigeon
On Android it works just fine but on iOS, whenever I try to call the method written in swift (on button press), it throws a PlatformException(channel-error, Unable to establish connection on channel., null, null)
I've setup the flutter starting project and then I am calling the native methods when the floatingActionButton is pressed. When the native method is executed, it returns a String saying "Hello from Android" on the android side and "Hello from Xcode" on iOS.
main.dart file:
import 'package:flutter/material.dart';
import 'package:platforms/pigeon.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
Api api = Api();
final searchReply = await api.search(SearchRequest());
print(searchReply.result);
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
MainActivity.java file:
package com.example.platforms;
import android.os.Bundle;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
private class Api implements Pigeon.Api {
#Override
public Pigeon.SearchReply search(Pigeon.SearchRequest arg) {
Pigeon.SearchReply searchReply = new Pigeon.SearchReply();
searchReply.setResult("Hello from Android!");
return searchReply;
}
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Pigeon.Api.setup(getFlutterEngine().getDartExecutor().getBinaryMessenger(), new Api());
}
}
AppDelegate.swift file:
import UIKit
import Flutter
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate, Api {
var engine: FlutterEngine = {
let result = FlutterEngine.init()
// This could be `run` earlier in the app to avoid the overhead of doing it the first time the
// engine is needed.
result.run()
return result
}()
func search(_ input: SearchRequest, error: AutoreleasingUnsafeMutablePointer<FlutterError?>) -> SearchReply? {
let reply = SearchReply()
reply.result = "Hello from Xcode!!!!"
return reply
}
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
ApiSetup(engine.binaryMessenger, self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Result of onPressed on Android:
Result of onPressed on iOS:
What's also worth noting is that when I call the code in onPressed in the didChangedDependencies function, the code in swift does run, and the result does show up, but the same exception is then thrown again. Which means that the channel does get created, I am just using it wrong.
When this is added to main.dart
#override
void didChangeDependencies() async {
Api api = Api();
final searchReply = await api.search(SearchRequest());
print(searchReply.result);
super.didChangeDependencies();
}
It prints the result from swift and then throws the same error.

I think the BinaryMessenger is getting set incorrectly.
I tried this and it worked:
In the didFinishLaunchingWithOptions method get the rootViewController and cast it as FlutterBinaryMessenger:
let rootViewController : FlutterViewController = window?.rootViewController as! FlutterViewController
// get binaryMessenger
let binaryMessenger = rootViewController as! FlutterBinaryMessenger
// set binaryMessenger
ApiSetup(binaryMessenger, self)
The complete method becomes like this:
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self);
let rootViewController : FlutterViewController = window?.rootViewController as! FlutterViewController
// get binaryMessenger
let binaryMessenger = rootViewController as! FlutterBinaryMessenger
// set binaryMessenger
ApiSetup(binaryMessenger, self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

Related

how create a RIVE animation with flutter

I want to create a RIVE animation with flutter. I followed a tutorial in YouTube. I wrote the same thing but when I execute two errors is displayed
(RiveFile.import (data);
file.mainArtboard;)
Here is the code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rive/rive.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyPage(),
);
}
}
class MyPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Using Rive'),
),
body: RocketContainer());
}
}
class RocketContainer extends StatefulWidget {
#override
_RocketContainerState createState() => _RocketContainerState();
}
class _RocketContainerState extends State<RocketContainer> {
Artboard _artboard;
RiveAnimationController _rocketController;
#override
void initState() {
_loadRiveFile();
super.initState();
}
void _loadRiveFile() async {
final bytes = await rootBundle.load('assets/rocket.riv');
final file = RiveFile.import(bytes);
setState(() {
_artboard = file.mainArtboard;
});
}
void _launch() async {
_artboard.addController(
_rocketController = SimpleAnimation('launch'),
);
setState(() => _rocketController.isActive = true);
}
void _fall() async {
_artboard.addController(
_rocketController = SimpleAnimation('fall'),
);
setState(() => _rocketController.isActive = true);
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - 250,
child: _artboard != null
? Rive(
artboard: _artboard,
fit: BoxFit.cover,
)
: Container()),
TextButton(onPressed: () => _launch(), child: Text('launch')),
TextButton(onPressed: () => _fall(), child: Text('fall'))
],
);
}
}
errors:
The current Dart SDK version is 2.10.5.
Because animation depends on cupertino_icons >=1.0.1 which requires SDK version >=2.12.0-0 <3.0.0, version solving failed.
pub get failed (1; Because animation depends on cupertino_icons >=1.0.1 which requires SDK version >=2.12.0-0 <3.0.0, version solving failed.)
*error: Instance member 'import' can't be accessed using static access. (static_access_to_instance_member at [animation] lib\main.dart:47)
*error: The getter 'mainArtboard' isn't defined for the type 'bool'. (undefined_getter at [animation] lib\main.dart:50)
You could have a look at the example provided with the updated and latest documentation of Rive in their official Github repository.
Control playing and pausing a looping animation:
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class PlayPauseAnimation extends StatefulWidget {
const PlayPauseAnimation({Key? key}) : super(key: key);
#override
_PlayPauseAnimationState createState() => _PlayPauseAnimationState();
}
class _PlayPauseAnimationState extends State<PlayPauseAnimation> {
// Controller for playback
late RiveAnimationController _controller;
// Toggles between play and pause animation states
void _togglePlay() =>
setState(() => _controller.isActive = !_controller.isActive);
/// Tracks if the animation is playing by whether controller is running
bool get isPlaying => _controller.isActive;
#override
void initState() {
super.initState();
_controller = SimpleAnimation('idle');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RiveAnimation.network(
'https://cdn.rive.app/animations/vehicles.riv',
controllers: [_controller],
// Update the play state when the widget's initialized
onInit: () => setState(() {}),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _togglePlay,
tooltip: isPlaying ? 'Pause' : 'Play',
child: Icon(
isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}
}
To play an animation from an asset bundle, use:
RiveAnimation.asset('assets/vehicles.riv'
in place of
RiveAnimation.network('https://cdn.rive.app/animations/vehicles.riv',
This line:
_controller = SimpleAnimation('idle');
attempts to play an animation called 'idle'. If your animation is named differently, try replacing the name here.

Methods are not getting executed in the written order inside onPressed

Im trying to create a flutter app with a simple raised button that does the following:
sends an sms in the background using the sms package opens a webpage
2. in the app(only for 5 seconds) using url_launcher opens the phones
3. native app for making a voice call with the onPressed property.
And I wanted it to be in this order so that I can make the phone call at the end. However, the inside the onPressed opens the native phone call app first, which doesnt let my web page open unless I exit out of the phone call app.
Im having a hard time understanding why the phone call native app is opened first, even though I make the call the _makePhoneCall() method only after I make the _launchInApp(toLaunch) call. sendSMS() is being called correctly
How can I set this in a way that the phone call native app is called only after the webpage is opened in the app and follows the order? Any help would be great
Below is the piece of code:
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:sms/sms.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Packages testing',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Packages testing'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _phone = '';
_launchInApp(String url) async {
if (await canLaunch(url)) {
await launch(
url,
forceSafariVC: true,
forceWebView: true,
headers: <String, String>{'my_header_key': 'my_header_value'},
);
} else {
throw 'Could not launch $url';
}
}
_makePhoneCall(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
void sendSMS() {
SmsSender sender = new SmsSender();
sender.sendSms(new SmsMessage(_phone, 'Testing Handset'));
}
#override
Widget build(BuildContext context) {
const String toLaunch = 'https://flutter.dev/';
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
onChanged: (String text) => _phone = text,
decoration:
const InputDecoration(hintText: 'Phone Number')),
),
FlatButton(
onPressed: () => setState(() {
sendSMS();
_launchInApp(toLaunch);
_makePhoneCall('tel:$_phone');
}),
child: const Text('Run All'),
),
const Padding(padding: EdgeInsets.all(16.0)),
],
),
],
),
);
}
}
You will have to use the await keyword before the _launchInApp function to make it work properly. Try the following code.
FlatButton(
onPressed: () aync {
sendSMS();
await _launchInApp(toLaunch);
_makePhoneCall('tel:$_phone');
}),
child: const Text('Run All'),
),
You created async functions but when you called them you did not specify that you want to wait for them to complete. Add the await keyword in OnPressed

Flutter get source URL along with Text shared from web page

I am new to Flutter. I want to get the text along with its source. Currently, I am using receive_sharing_intent which is serving the purpose of TextStream and Url individually.
I want to share the copied text from the browser and keep its URL along with it.
I followed this doc hence my main.dart is same as:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
StreamSubscription _intentDataStreamSubscription;
List<SharedMediaFile> _sharedFiles;
String _sharedText;
#override
void initState() {
super.initState();
// For sharing or opening urls/text coming from outside the app while the app is in the memory
_intentDataStreamSubscription =
ReceiveSharingIntent.getTextStream().listen((String value) {
setState(() {
_sharedText = value;
});
}, onError: (err) {
print("getLinkStream error: $err");
});
// For sharing or opening urls/text coming from outside the app while the app is closed
ReceiveSharingIntent.getInitialText().then((String value) {
setState(() {
_sharedText = value;
});
});
}
#override
void dispose() {
_intentDataStreamSubscription.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
const textStyleBold = const TextStyle(fontWeight: FontWeight.bold);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
children: <Widget>[
Text("Shared files:", style: textStyleBold),
Text(_sharedFiles?.map((f)=> f.path)?.join(",") ?? ""),
SizedBox(height: 100),
Text("Shared urls/text:", style: textStyleBold),
Text(_sharedText ?? "")
],
),
),
),
);
}
}
Any suggestions on how to proceed to this or any Reference will work.
Thanks!!

Platform location permission error with Flutter isolates

I'm attempting to run location updates on a Flutter isolate thread, the error is only present when running an isolate. Location requests works without issues on the main thread. The goal here is to run this as a background service, working with dart code only.
I am using Geolocator plugin for location requests.
This is the error I am facing when starting the isolate:
Exception has occurred. FlutterError
(ServicesBinding.defaultBinaryMessenger was accessed before the
binding was initialized.
I have tried to include the WidgetsFlutterBinding.ensureInitialized() before runApp but without results.
Looking at the call stack of the error, it seems problems occur at the android location platform call: checkPermissionStatus
This happens regardless of what location plugin I am using, it stops at the permission status check.
I have figured it could have something to do with awaiting location permission user input, but this check will fail on a non-ui thread?
See this simple main.dart file for an example:
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Isolate location test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Isolate location test'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Isolate isolate;
bool isRunning = false;
String output = '';
ReceivePort receivePort;
void start() async {
receivePort = ReceivePort();
await Isolate.spawn(locationUpdate, receivePort.sendPort);
receivePort.listen((dynamic data) {
setState(() {
isRunning = true;
});
}, onDone: () {
print("done");
});
}
void stop() {
if (isolate != null) {
setState(() {
isRunning = false;
});
receivePort.close();
isolate.kill(priority: Isolate.immediate);
isolate = null;
}
}
static void locationUpdate(SendPort sendPort) async {
Geolocator().checkGeolocationPermissionStatus().then((status) {
sendPort.send(status);
});
// Geolocator().getCurrentPosition().then((pos) {
// sendPort.send(pos);
// });
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(child: Text(output)),
floatingActionButton: FloatingActionButton(
onPressed: isRunning ? stop : start,
child: Icon(isRunning ? Icons.stop : Icons.play_circle_filled),
),
);
}
}

Can I integrate tawk.to into the nav bar of my Flutter app?

I'd like to integrate my websites tawk.to chat button into my Flutter app in some way. Maybe loading a webview at all times only showing the icon? But then when it's clicked I want it to maximize over the current content, and also for the user to receive a vibration or notification when a message is received in the chat.
Here's the widget code for tawk.to:
<!--Start of Tawk.to Script-->
<script type="text/javascript">
var Tawk_API=Tawk_API||{}, Tawk_LoadStart=new Date();
(function(){
var s1=document.createElement("script"),s0=document.getElementsByTagName("script")[0];
s1.async=true;
s1.src='https://embed.tawk.to/5c8306ab101df77a8be1a645/default';
s1.charset='UTF-8';
s1.setAttribute('crossorigin','*');
s0.parentNode.insertBefore(s1,s0);
})();
</script>
<!--End of Tawk.to Script-->
I want to use tawk.to as that's what I'm using on my website right now aswell, having 2 different chat systems would make everything a lot harder.
Any other suggestions for solutions to the problem are also welcome.
Main.dart here:
import 'package:flutter/material.dart';
import 'home_widget.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';
void main() => runApp(App());
class App extends StatelessWidget {
static FirebaseAnalytics analytics = FirebaseAnalytics();
static FirebaseAnalyticsObserver observer =
FirebaseAnalyticsObserver(analytics: analytics);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My Flutter App',
navigatorObservers: <NavigatorObserver>[observer],
home: Home(
analytics: analytics, //
observer: observer, //
),
);
}
}
My home widget currently looks like this:
import 'package:flutter/material.dart';
import 'placeholder_widget.dart';
import 'homepage.dart';
import 'reader.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';
class Home extends StatefulWidget {
Home({Key key, this.title, this.analytics, this.observer}) //
: super(key: key); //
final String title; //
final FirebaseAnalytics analytics; //
final FirebaseAnalyticsObserver observer; //
#override
State<StatefulWidget> createState() {
return _HomeState(analytics, observer);
}
}
class _HomeState extends State<Home> {
_HomeState(this.analytics, this.observer); //
final FirebaseAnalyticsObserver observer; //
final FirebaseAnalytics analytics; //
int _currentIndex = 0;
final List<Widget> _children = [
Homepage(),
MyApp(),
PlaceholderWidget(Colors.green) // TODO: I want my chat button here
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: _children[_currentIndex], // new
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped, // new
currentIndex: _currentIndex, // new
items: [
new BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
new BottomNavigationBarItem(
icon: Icon(Icons.photo_camera),
title: Text('blah'),
),
new BottomNavigationBarItem(
icon: Icon(Icons.person),
title: Text('Chat')
)
],
),
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
Use this package flutter_tawk.
import 'package:flutter_tawk/flutter_tawk.dart';
Tawk(
directChatLink: 'YOUR_DIRECT_CHAT_LINK',
visitor: TawkVisitor(
name: 'Ayoub AMINE',
email: 'ayoubamine2a#gmail.com',
),
)
I solved it. The best way I found was using javascript inside of the webview, and then you can evaluate javascript in the webview through the webview controller.
I made Tawk.to default to hiding on my website, and to show it you simply do the following:
Declare a controller for the webview. At the start of your class add:
WebViewController _controller;
Then inside of your WebView() widget:
new WebView(
initialUrl: 'google.com',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) async {
_controller = webViewController;
//I've left out some of the code needed for a webview to work here, fyi
},
),
And finally you can make a button in your appbar run the javascript code to open tawk:
IconButton(
icon: Icon(Icons.message),
onPressed: () {
_controller.evaluateJavascript('Tawk_API.showWidget();');
_controller.evaluateJavascript('Tawk_API.maximize();');
},
),

Categories

Resources