I am using the official Flutter (Flutter 1.22.6 • channel stable) camera Plugin (https://pub.dev/packages/camera) within 2 Flutter apps. The stripped-down code sniped is used in both apps (pure copy & paste).
However, results in both apps are different in terms of their orientation. The problem is not to provide the user a correct output on the screen. This is possible with either CameraPreview or RotatedBox. I am using the buildPreview() method from the CameraController to see "What the camera sees" (at least I hope it does).
What is needed is to record a picture from the CameraController-Stream in the correct orientation (straight up) to process it with some AI SDKs.
Or as a workaround some easy and lightweight way to turn it 90 degree as an Uint8List. What is not possible is to do it just with some meta-data.
Should be worth mentioning the more complex app (where the rotation is wrong) has two restrictions:
In the iOS project under General there is no Device Orientation selected and a bit below "Requires full screen" is checked.
The app is initialized in the main-Method with await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
What I already tried without success:
Making the orientation and "Requires full screen" configuration the same in both apps.
Setting the SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); in the initState
Use await controller.lockCaptureOrientation(DeviceOrientation.portraitUp);
Removed any camera plugins restrictions from the iOS Podfile
I have no clue why there is this difference. Any thoughts what I should try out or this is would be amazing!
import 'dart:async';
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Timer timer;
CameraController controller;
CameraImage image;
Future<CameraController> init;
#override
void initState() {
init = initialize();
super.initState();
}
Future<CameraController> initialize() async {
var cameras = await availableCameras();
controller = CameraController(cameras[0], ResolutionPreset.medium);
await controller.initialize();
controller.startImageStream((image) {
this.image = image;
});
timer = Timer.periodic(Duration(seconds: 5), (timer) async {
print('start scanning');
});
return controller;
}
Future<void> outText(CameraImage image) async {
Uint8List bytes = image.planes[0].bytes;
// AI magic...
return;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: FutureBuilder<CameraController>(
future: init,
builder: (context, snapshot) {
if (snapshot.hasData)
return Center(
child: snapshot.hasData
? SizedBox(child: snapshot.data.buildPreview())
: CircularProgressIndicator());
else
return SizedBox.shrink();
},
),
),
);
}
}
After a few more hours the only difference is the camera plugin version between both results.
It looks like a bug that happens with camera: ^0.7.0 and later.
To get the correct result the workaround would be to use: camera: ^0.6.4+5 or earlier.
A bug is opened there: https://github.com/flutter/flutter/issues/76210
Related
In Flutter, I am using Navigator 2.0 using go_router.
I want deeplink in my app to work properly and my app has some properties which needs to be initialized before going to any screen so I have put my MaterialApp.router(...) in a conditional boolean and Splash screen is shown while app is being initialized.
But deeplink doesn't redirect to given path when app is closed on iOS, it just opens the app.
Everything is working fine in android.
It starts working fine in iOS too when I remove async app initialization function from MaterialApp.
What am I doing wrong? Is there better way to initialze app instead of a conditional check in MaterialApp.
I have created a basic reproducible code repo https://github.com/sgshivamgarg8/testapp. Please try it out. To test deeplinks I have put required scripts in scripts/ folder.
Future<void> _appInit() async {
setState(() {
isLoading = true;
});
await Future.delayed(
const Duration(seconds: 1)); // commenting this line solves the issue
setState(() {
isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return isLoading
? const CircularProgressIndicator()
: MaterialApp.router(
routerConfig: _router,
title: 'Test',
theme: ThemeData.light(),
debugShowCheckedModeBanner: false,
);
Below the code loads several tweets in WebViews. This code works flawlessly in Android but produces a different result in iOS Simulator. No errors are produced in logs and nothing in flutter analyze pertain to this page and its classes.
Included <key>io.flutter.embedded_views_preview</key><true/> in info.plist
Widget build(BuildContext context) {
var child;
//print(_tweetHTML);
if (_tweetHTML != '' && _tweetHTML.length > 0) {
final downloadUrl = Uri.file(_filename).toString();
print(downloadUrl); //This prints the expected HTML file
// Create the WebView to contian the tweet HTML
final webView = WebView(
initialUrl: downloadUrl,
javascriptMode: JavascriptMode.unrestricted,
gestureNavigationEnabled: true,
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith(downloadUrl)) {
//print('allowing navigation to $request');
return NavigationDecision.navigate;
} else if (request.isForMainFrame) {
//print('allowing navigation to $request');
_launchURL(request.url);
return NavigationDecision.prevent;
} else {
//print('blocking navigation to $request}');
return NavigationDecision.prevent;
}
},);
final box = LimitedBox(
maxHeight: 500.0,
child: webView,
);
child = box;
} else {
child = Text('Loading...');
}
return Container(child: child);
}
Expected results:
As you can see this works in Android:
Actual results:
However on iOS:
Option 1: add this to html body
<script id="twitter-wjs" type="text/javascript" async defer src="//platform.twitter.com/widgets.js"></script>
Option 2: Check out this widget on github TweetWebView for Flutter
Option 3: use Flutter WebView Plugin by fluttercommunity.dev, BUT This webview is not integrated in the widget tree, it is a native view on top of the flutter view. You won't be able see snackbars, dialogs, or other flutter widgets that would overlap with the region of the screen taken up by the webview.
There is a weird bug that occurs when I try to play an audio file(.mp3) from assets, in iOS. I'm using audioplayers plugin for this. My task is to play an audio file (either from local storage or assets), after some delay, while the app is not in foreground, that is while the screen is locked or app in in recent tray. The problem is, when I try to play the audio file after any timer function when iPhone is locked (ex. after timer.delay, Future.delay, etc) the app doesn't plays it. The app plays it after unlocking the iPhone. Also a point is to be noted that EVERYTHING WORKS as intended in DEBUG version of the app, and NOT IN RELEASE version. One more thing, I have added background audio permission in info.plist, which is necessary for playing audio in background. In Android, everything is working fine. Also , I have tried the Audio_service plugin, but it didn't worked as well.
My dummy code for app is as follows:
class AudioTesting extends StatefulWidget {
#override
_AudioTestingState createState() => _AudioTestingState();
}
class _AudioTestingState extends State<AudioTesting> {
#override
void initState() {
AudioPlayer _audioPlayer = AudioPlayer();
AudioCache _audioCache = AudioCache();
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return AudioServiceWidget(
child: Scaffold(
appBar: AppBar(
title: Text('Testing Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(onPressed: (){
Future.delayed(Duration(seconds: 5),(){
_audioCache.load('$test.mp3');
_audioPlayer = await _audioCache.play('$test.mp3');
//This doesnt works in release mode, but works in debug mode, when played in Locked state
});
},
child: Text('Future.delay func'),
),
RaisedButton(onPressed: (){
sleep(Duration(seconds: 5));
_audioCache.load('$test.mp3');
_audioPlayer = await _audioCache.play('$test.mp3');
// This works completely fine in both version
},
child: Text('sleep func'),
),
],
),
),
),
);
}
}
You can reproduce this bug as follows:
Copy paste the above code snippet.
build a release version and deploy it on any physical iOS device.
Click on the buttons and lock your iPhone.
In my case, I used iPhone 8 with iOS 14.4
Are there any possible alternative solutions that would let me play music in background, when triggered by some functions in iOS?
OrientationBuilder reports the orientation change after the full transformation has taken place, then the rebuild occurs after that.
Is there a way to act before the orientation initiates? I am not trying to pre-empt the rotation, but make changes simutaneously, not after.
The goal:
Device is rotated.
Detect this and rebuild UI to show overlay.
Flutter performs its own tranformation, rotating UI to new orientation.
After fixed time period simply rebuild to hide overlay.
The challenge, how to fulfil point 2 before 3 occurs?
Yes, you can get the orientation change earlier using WidgetsBindingObserver by overriding didChangeMetrics.
How to use didChangeMetrics
You can simply mixin WidgetBindingObserver in a State implementation of a stateful widget:
class _FooState extends State with WidgetsBindingObserver {
#override
void didChangeMetrics() {
// This will be triggered by changes in orientation.
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
Determining the orientation
Orientation is determined by the aspect ratio of the available size. This means that you can get the orientation in didChangeMetrics using the following code:
final orientation = WidgetsBinding.instance.window.physicalSize
.aspectRatio > 1 ? Orientation.landscape : Orientation.portrait;
Example
I have constructed an example StatefulWidget that compares the OrientationBuilder callback to didChangeMetrics:
import 'package:flutter/material.dart';
void main() {
runApp(OrientationListener());
}
class OrientationListener extends StatefulWidget {
#override
_OrientationListenerState createState() => _OrientationListenerState();
}
class _OrientationListenerState extends State<OrientationListener>
with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeMetrics() {
print('$WidgetsBindingObserver metrics changed ${DateTime.now()}: '
'${WidgetsBinding.instance.window.physicalSize.aspectRatio > 1 ? Orientation.landscape : Orientation.portrait}');
}
#override
Widget build(BuildContext context) {
return MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
child: OrientationBuilder(
builder: (context, orientation) {
print('$OrientationBuilder rebuild ${DateTime.now()}: $orientation');
return Container();
},
),
);
}
}
Results
Running this example shows the following times for the two functions:
WidgetsBindingObserver metrics changed 2020-08-22 14:47:01.690172: Orientation.portrait
WidgetsBindingObserver metrics changed 2020-08-22 14:47:01.706574: Orientation.landscape
OrientationBuilder rebuild 2020-08-22 14:47:01.760589: Orientation.landscape
WidgetsBindingObserver metrics changed 2020-08-22 14:47:06.537083: Orientation.landscape
WidgetsBindingObserver metrics changed 2020-08-22 14:47:06.549545: Orientation.portrait
OrientationBuilder rebuild 2020-08-22 14:47:06.603859: Orientation.portrait
WidgetsBindingObserver metrics changed 2020-08-22 14:47:10.423787: Orientation.portrait
WidgetsBindingObserver metrics changed 2020-08-22 14:47:10.442866: Orientation.landscape
OrientationBuilder rebuild 2020-08-22 14:47:10.501729: Orientation.landscape
WidgetsBindingObserver metrics changed 2020-08-22 14:47:13.639545: Orientation.landscape
WidgetsBindingObserver metrics changed 2020-08-22 14:47:13.658906: Orientation.landscape
WidgetsBindingObserver metrics changed 2020-08-22 14:47:13.672025: Orientation.portrait
OrientationBuilder rebuild 2020-08-22 14:47:13.730771: Orientation.portrait
So in my case, the difference in detection was about 0.06 seconds.
Observations
As you can see from above, the difference is insignificant (I would say). So I am not even sure if this will be useful to you.
Moreover, I have observed that the OrientationBuilder callback is actually called at the start of the device rotation - at least on my Android emulator. This would mean that you can rebuild your UI before the rotation happens.
I hope this was somehow helpful to you :)
As you can see in this link, there is no "natural" way to do what you want: https://github.com/flutter/flutter/issues/16322
Try to make something using this:
Lock the orientation: https://dev.to/mightytechno/how-to-change-screen-orientation-in-flutter-32c1
Listen to device rotation changes with this package (since the orientation will not change): https://pub.dev/packages/native_device_orientation
Make your customization
Set the new orientation using the link from step 1
You may try animated container for a better effect, but you would need to handle all the screen position and rotation manually before set the new orientation:
https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html
Something you might want to try is writing a platform channel method to get the sensor data from the accelerometer and then feed it to something like Transform.rotate().
I want to open a bunch of music app links using links data I have in firebase. I want to open, amazonPrimeMusic, Ganna, Spotify, Wynk, JioSavaan to name some.
Widget buildResultCard(data) {
List items = [Text(data['Ganna']),
IconButton(icon:Icon(Icons.all_inclusive),
onPressed: ()=> {Text("Ganna")}
),
Text(data['Wynk']),
IconButton(icon:Icon(Icons.all_inclusive),
onPressed: ()=> {Text("Ganna")}
),
Text(data['JioSavaan']),
IconButton(icon:Icon(Icons.all_inclusive),
onPressed: ()=> {Text("Ganna")}
),
Text(data['PrimeMusic']),
IconButton(icon:Icon(Icons.all_inclusive),
onPressed: ()=> {Text("Ganna")}
)
];
return ListView.builder(
padding: EdgeInsets.only(top: 20),
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return items[index];
},
);
}
when I tap the button in the list it should open up the particular app for which the link is, for example for AmazonPrimeMusic link, it should open the Amazon music app.
add this to the pubspec.yaml file under dependencies-
device_apps:
android_intent:
url_launcher:
and add these to the top -
import 'package:device_apps/device_apps.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:android_intent/android_intent.dart';
and here is the sample code -
_openJioSavaan (data) async
{String dt = data['JioSavaan'] as String;
bool isInstalled = await DeviceApps.isAppInstalled('com.jio.media.jiobeats');
if (isInstalled != false)
{
AndroidIntent intent = AndroidIntent(
action: 'action_view',
data: dt
);
await intent.launch();
}
else
{
String url = dt;
if (await canLaunch(url))
await launch(url);
else
throw 'Could not launch $url';
}
}
You can use flutter_appavailability package. This plugin allows you to check if an app is installed in mobile and using this plugin you can launch an app.
If already installed then launch otherwise open link in WebView using url_launcher.
Hello you actually need two packages. Check the versions before you use them. First of all you need the id of the app. For example for facebook lite the id is com.facebook.lite. You acn find the id if you go to playstore click share and cope the link. The link for facebook lite is https://play.google.com/store/apps/details?id=com.facebook.lite from this one you can easily understand that the id is after "id=". Its the same on the other apps too.
device_apps: ^2.1.1
url_launcher: ^6.0.3
try {
///checks if the app is installed on your mobile device
bool isInstalled = await DeviceApps.isAppInstalled('si.modula.android.instantheartrate');
if (isInstalled) {
DeviceApps.openApp("si.modula.android.instantheartrate");
} else {
///if the app is not installed it lunches google play store so you can install it from there
launch("market://details?id=" +"si.modula.android.instantheartrate");
}
} catch (e) {
print(e);
}
so the code above check if you have already installed the application. If you have done it it will lunch the application if not it is going to open google playstore so you can see it there. It works only for android devices.
Updated :
url_launcher: ^6.1.4
void launchAnotherApp() async {
if (!await launchUrl(Uri.parse("https://www.instagram.com/username/"),
mode: LaunchMode.externalApplication)) {
throw 'Could not launch ';
}
}
It opens another app on your phone. If targeted app not installed on your device it will open in webview.
I think you can use external_app_launcher: ^3.0.0
https://pub.dev/packages/external_app_launcher
You can easily do it with the help of external_app_launcher.
A Flutter plugin that helps you to open another app from your app. The package asks you for four parameters out of which two are mandatory.
Example code:
RaisedButton(
color: Colors.blue,
onPressed: () {
LaunchApp.openApp(
androidPackageName: 'net.pulsesecure.pulsesecure',
iosUrlScheme: 'pulsesecure://',
appStoreLink: 'itms-apps://itunes.apple.com/us/app/pulse-secure/id945832041',
);
// Enter the package name of the App you want to open and for iOS add the URLscheme to the Info.plist file.
// The `openStore` argument decides whether the app redirects to PlayStore or AppStore.
// For testing purpose you can enter com.instagram.android
},
child: Container(
child: Center(
child: Text("Open",
textAlign: TextAlign.center,
),
),
),
)