Flutter: problem with SystemChrome.setPreferredOrientations when I use it with an iPad - ios

In my project, I only need to have one page in landscape mode and not in portrait mode, while the rest of the pages are in portrait and landscape mode.
As per the documentation, I used SystemChrome.setPreferredOrientations
#override
void initState() {
// set the screen in landscape mode
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
]);
super.initState();
}
and
#override
void dispose() {
// restore the
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
]);
super.dispose();
}
when I exit the page to restore the portrait mode.
Now, while with the iPhone it works perfectly, with the iPad it is as if it were not there and both landscape and portrait are possible.
Of course as I don't want the whole application in landscape mode but only one page I can't act on info.plist.
Has anyone had the same problem?
Thanks

In dispose keep only portrait up
#override
void dispose() {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
super.dispose();
}

Related

Flutter Camera Package: Stream/Picture from Camera is -90° rotated

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

Listen to Orientation State in Flutter; before & after rotation

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().

Flutter - conditionally hide status bar if app is in landscape mode

In Related question, app can hide the status bar with
SystemChrome.setEnabledSystemUIOverlays([]);
But if I surround that with a media query to detect orientation in the main app, it causes the app to error because there is no material ancestor widget.
// app error
if (MediaQuery.of(context).orientation == Orientation.landscape) {
SystemChrome.setEnabledSystemUIOverlays([]);
}
Is there a way to conditionally hide the status bar in Flutter for the entire app, if the device rotates to landscape mode?
MediaQuery widget can be accessed only when the ancestor of the widget that you access MediaQuery from is a material widget.
A more generic solution without MediaQuery would be like:
First
import "dart:ui" as ui;
then
Size s = ui.window.physicalSize/ui.window.devicePixelRatio;
bool landscape = s.width>s.height;
if (landscape) {
SystemChrome.setEnabledSystemUIOverlays([]);
}
Or
Using Orientation builder as root. Inside the build function,
new OrientationBuilder(
builder: (BuildContext context, Orientation orientation){
if (orientation == Orientation.landscape) {
SystemChrome.setEnabledSystemUIOverlays([]);
} else {
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values)
}
return new Scaffold(...
}
);
Hope that helped!

libgdx - iOS landscape not working right with scene2d

I'm having an issue with libGDX's otherwise great scene2d ui library when running my app in landscape mode. My program works as expected in both the android simulator and the desktop environment, but when I run it on the iOS simulator or a real device, the main menu is drawn as if the phone were still in the portrait orientation, and the just cropped to fit in a landscape view. At first, I assumed I had just screwed up, as I'm still learning the api, but the app works just as expected on android. I have configured my game to run in landscape mode on both iOS and android.
On the android simulator, the menu works as expected. The simulator is in landscape orientation, and properly displays the menu.
On iOS, the menu is off center and unclickable.
I pretty sure that the problem has to do with landscape mode, because everything works fine in portrait orientation. Here's my code for the MainMenuScreen, which extends a ScreenAdapter.
package bakpakin.techgame;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.ScreenAdapter;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
/**
* Created by Calvin on 9/25/14.
*/
public class MainMenuScreen extends ScreenAdapter {
private TechGame game;
private Stage stage;
private Skin skin;
private Table table;
public MainMenuScreen(TechGame game) {
this.game = game;
}
#Override
public void show() {
this.stage = new Stage();
this.skin = new Skin();
skin.add("default", Assets.superScript48);
skin.add("title", Assets.superScript128);
TextButtonStyle textButtonStyle = new TextButtonStyle();
textButtonStyle.font = skin.getFont("default");
textButtonStyle.pressedOffsetY = 4;
skin.add("default", textButtonStyle);
Label.LabelStyle labelStyle = new Label.LabelStyle();
labelStyle.font = skin.getFont("title");
labelStyle.fontColor = Color.CYAN;
skin.add("default", labelStyle);
// Create a table that fills the screen. Everything else will go inside this table.
table = new Table();
table.setFillParent(true);
stage.addActor(table);
final TextButton play = new TextButton("Play", skin);
final TextButton about = new TextButton("About", skin);
final TextButton quit = new TextButton("Quit", skin);
play.addListener(new ChangeListener() {
#Override
public void changed(ChangeEvent event, Actor actor) {
Assets.button1Sound.play(1, 1, 0);
}
});
about.addListener(new ChangeListener() {
#Override
public void changed(ChangeEvent event, Actor actor) {
Assets.button1Sound.play(1, 1, 0);
}
});
quit.addListener(new ChangeListener() {
#Override
public void changed(ChangeEvent event, Actor actor) {
Assets.button1Sound.play(1, 1, 0);
Gdx.app.exit();
}
});
final Label title = new Label("My Game", skin);
VerticalGroup vg = new VerticalGroup();
vg.pad(60);
table.add(title);
table.row();
table.add(vg);
vg.addActor(play);
vg.addActor(about);
vg.addActor(quit);
Gdx.input.setInputProcessor(stage);
}
#Override
public void render(float delta) {
GL20 gl = Gdx.gl;
gl.glClearColor(0, 0, 0, 1);
gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
stage.act(delta);
stage.draw();
}
#Override
public void dispose() {
stage.dispose();
skin.dispose();
}
}
IOS 7 or 8? I heard there are some iOS 8 rendering issues
Apparently this is fixed in the latest snapshot (see github.com/libgdx/libgdx/issues/2386). With a new stable release promised real-soon-now, I'm sure the fix will be folded in if you're not brave enough to run snapshot builds

How to fix alignment for vertical and portrait screen in BlackBerry?

I want to do application which will not change UI even if screen is changed to portrait or landscape direction. How to do that?
I have made a static method in one of my library classes:
public static void disableOrientationChange()
{
// force app to use only portrait mode
int directions = Display.DIRECTION_NORTH;
UiEngineInstance engineInstance = Ui.getUiEngineInstance();
if (engineInstance != null)
{
engineInstance.setAcceptableDirections(directions);
}
}
The trick is that this code only works for screens created after running it. So you must run the code BEFORE you show your first screen.
I call this method from my Application.main(String args) method, just before the call to enterEventDispatcher().
public static void main(String[] args)
{
MyApp app = new MyApp();
/*
* BlackBerry OS 5.0.0
* Disable screen orientation changes
*/
ScreenUtils.disableOrientationChange();
// enters the event processing loop thread if required
if (!app.isHandlingEvents())
{
app.enterEventDispatcher();
}
}
you can use this code to set the orientation to portrait. After that even if the device is held in landscape, the orientation wont change.
Ui.getUiEngineInstance().setAcceptableDirections(Display.DIRECTION_PORTRAIT);

Resources