Flutter Camera Preview not rotating with phone orientation - android

I'm new to Dart/Flutter and currently using the Flutter Camera Plugin but I am running into a problem with the CameraPreview for when the phone turns to landscape mode. The images stay vertical and don't rotate the 90degrees with the phone.
I have tried countless things including Transform.rotate(), but I can't seem to get the image to both, fill the screen and rotate 90degrees.
The pictures that were taken while the phone was sideways still saved in the correct orientation when I view them, but using the _cameraPreviewWidget.
Normal
Landscape

Adding SystemChrome.setPreferredOrientations() to my initState() & dispose() functions solved this problem for me.
import 'package:flutter/services.dart';
...
#override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}
#override
void dispose() {
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}

A package call camera_camera https://pub.dev/packages/camera_camera
has do great work and provide great feature you can reference his source code or fork directly.
about rotate issue, please use this package https://pub.dev/packages/native_device_orientation
and wrap body like this
return Scaffold(
body: NativeDeviceOrientationReader(builder: (context) {
NativeDeviceOrientation orientation =
NativeDeviceOrientationReader.orientation(context);
you can reference full code at
https://github.com/marslord/camera/blob/master/lib/cam.dart ,this is not camera_camera package and author use RotateBox
return RotatedBox(
quarterTurns: turns,
child: Transform.scale(
scale: 1 / controller.value.aspectRatio,
child: Center(
child: AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: CameraPreview(controller),
),
),
),
);
camera_camera package use this at line 76
return NativeDeviceOrientationReader(
useSensor: true,
builder: (context) {
NativeDeviceOrientation orientation =
NativeDeviceOrientationReader.orientation(context);
about fit screen issue
camera_camera package do this with below code full code is here https://github.com/gabuldev/camera_camera/blob/master/lib/page/camera.dart
return widget.mode ==
CameraMode.fullscreen
? OverflowBox(
maxHeight: size.height,
maxWidth: size.height *
previewRatio,
child: CameraPreview(
bloc.controllCamera),
)
: AspectRatio(
aspectRatio: bloc
.controllCamera
.value
.aspectRatio,
child: CameraPreview(
bloc.controllCamera),
);
execute result of camera_camera package can found on his github
execute result of https://github.com/marslord/camera
both check with real device and works
official camera plugin example rotate image can work with combine Native_device_orientation
and RotateBox , you can reference https://github.com/marslord/camera/blob/master/lib/cam.dart
but official camera plugin example fit screen issue will need to modify layout code
I would suggest use camera_camera's method, Stack CameraPreview and Button. it looks more like Native Camera App and easy to maintain landscape mode
official camera plugin example rotate image code snippet with combine Native_device_orientation and RotateBox
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Camera example'),
),
body: NativeDeviceOrientationReader(builder: (context) {
NativeDeviceOrientation orientation =
NativeDeviceOrientationReader.orientation(context);
int turns;
switch (orientation) {
case NativeDeviceOrientation.landscapeLeft:
turns = -1;
break;
case NativeDeviceOrientation.landscapeRight:
turns = 1;
break;
case NativeDeviceOrientation.portraitDown:
turns = 2;
break;
default:
turns = 0;
break;
}
return Column(
children: <Widget>[
Expanded(
child: RotatedBox(
quarterTurns: turns,
child: Transform.scale(
scale: 1 / controller.value.aspectRatio,
child: Container(
child: Padding(
padding: const EdgeInsets.all(1.0),
child: AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: Center(
child: _cameraPreviewWidget(),
),
),
),

When you rotate your phone while using a camera the preview will stay vertical, however when you view the image it will be in landscape, try with the default camera app and it would be the same
You can obviously rotate your UI that is on top of the camera preview when the phone rotates

My solution;
using with this libraries; https://pub.dev/packages/image and https://pub.dev/packages/native_device_orientation
Future<void> takeAPicture()async{
pictureShooting=true;
final XFile image=await cameraController!.takePicture();
img.Image? _capturedImage=img.decodeImage(await image.readAsBytes());
final NativeDeviceOrientation currentOrientation=await _nativeDeviceOrientationCommunicator.orientation(useSensor: true);
switch(currentOrientation){
case NativeDeviceOrientation.landscapeRight: _capturedImage=img.copyRotate(_capturedImage!, 90);break;
case NativeDeviceOrientation.landscapeLeft: _capturedImage=img.copyRotate(_capturedImage!, 270);break;
case NativeDeviceOrientation.portraitDown: _capturedImage=img.copyRotate(_capturedImage!, 180);break;
default:
}
takenImage=Uint8List.fromList(img.encodePng(_capturedImage!));
pictureShooting=false;
showImageApprovalScreen=true;
}

In my case, a real Android device in Landscape orientation would show the appropriate camera preview content, but because the device wasn't "allowed" to rotate (due to app manifest/plist settings), it was showing as a strip where the Left to Right wide pixels were showing in a rectangle on the left side going from Bottom to Top, where that rectangle was small in order to fit in that dimension... so most of the screen was white empty space. On emulators, this phenomenon didn't occur (rotation worked as desired). The solution, after much searching and troubleshooting was to put some code in to modify what orientations were allowed, only while the camera preview was being displayed:
//allows any rotation (while camera preview is open)
Future<void> _enableRotation() async {
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
}
// may block rotation; sets orientation back to OS defaults for the app
Future<void> _backToOriginalRotation() async {
await SystemChrome.setPreferredOrientations([]);
}
I used await _enableRotation(); in initializing an ancestor of CameraPreview and I used await _backToOriginalRotation(); in disposing.
I used WillPopScope's onWillPop callback and await Navigator.of(context).maybePop<String>>(filepath); to close the view when a picture had been captured (and after the image had been saved to temp directory at the path filepath), so that the Android back button would also run the dispose and rotation code via onWillPop.
EDIT: After testing on both iOS and Android actual devices, this fix worked on Android phones, but did not work on iPhones. iPhones still displayed the camera preview in a small rotated strip when the device is landscape. This is using camera package version 0.8.1+7.
EDIT2: After updating the iOS info.plist to allow any device orientation, this approach worked for iPhones too. However, there are still devices and situations where the preview orientation (and thus captured image orientation) are incorrect, leading to about 50% of captured landscape photos being saved sideways (in portrait orientation). Reproduction of the problem seems possible if you start your device in landscape orientation when you open the camera preview and don't rotate your device before capturing (or if device auto-rotate screen setting is off).

class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
VideoPlayerController _videoPlayerController;
ChewieController _chewieController;
#override
void initState() {
super.initState();
initializePlayer();
}
Future<void> initializePlayer() async {
// widget.url put url video file path
_videoPlayerController =
_videoPlayerController = VideoPlayerController.network(widget.url);
await Future.wait([_videoPlayerController.initialize()]);
_chewieController = ChewieController(
videoPlayerController: _videoPlayerController,
autoPlay: true,
looping: true,
);
}
bool useSensor = true;
bool portrait;
#override
Widget build(BuildContext context) {
return NativeDeviceOrientationReader(
builder: (context) {
NativeDeviceOrientation orientation =
NativeDeviceOrientationReader.orientation(context);
portrait = orientation == NativeDeviceOrientation.portraitUp;
if(orientation == NativeDeviceOrientation.landscapeLeft){
SystemChrome.setPreferredOrientations([
!portrait
? DeviceOrientation.landscapeLeft
: DeviceOrientation.portraitUp,
!portrait
? DeviceOrientation.landscapeLeft
: DeviceOrientation.portraitDown,
]);
}else{
SystemChrome.setPreferredOrientations([
!portrait
? DeviceOrientation.landscapeRight
: DeviceOrientation.portraitUp,
!portrait
? DeviceOrientation.landscapeRight
: DeviceOrientation.portraitDown,
]);
}
return _chewieController != null &&
_chewieController.videoPlayerController.value.isInitialized
? Chewie(
controller: _chewieController,
)
: Center(child: CircularProgressIndicator());
},
useSensor: useSensor,
);
}
#override
void dispose() {
_videoPlayerController.dispose();
_chewieController.dispose();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
super.dispose();
}
}

Related

PagewiseListView keeps loading new data, even when there is no scrolling

So my pageLoadController is calls on a function that fetches data from my backend services, page wise. This controller is defined in the initState like this
void initState() {
super.initState();
_pageLoadController = PagewiseLoadController(
pageSize: 10,
pageFuture: (pageIndex) {
return getDetails(pageIndex, 10);
});
}
My PagewiseListView widget is called inside a ListView, and the ListView is inside the Scaffold's body.
ListView(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text("Details",
style: TextStyle(
fontFamily: 'FuturaLT',
color: Theme.of(context).primaryColor,
fontSize: SizeConfig.safeBlockHorizontal * 5,
fontWeight: FontWeight.w400,
fontStyle: FontStyle.normal,
)),
PagewiseListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, entry, index) {
return _itemBuider(entry, SizeConfig.safeBlockVertical * 17);
},
pageLoadController: _pageLoadController,
)
where _itemBuilder returns the widget and displays the data in Entry.
Now, I have worked with pagewiseListViews in the past, so the expected behavior is that when the end of the list is reached (via scrolling), the pageLoad automatically handles the network call to fetch the next data, etc.
Code for getDetails(), just a generic outline for it, rest assured data here is being fetched properly
Future<List<Details>> getDetails(
int localListSize, int localListStart) async {
var responseJson = await new NetworkUtils().netWorkcall(localListSize,localListStart);
if (responseJson != null) {
List<Details> list ;
//map data to Details model
return list;
}
return Future.value();
}
In this case, the pageController continues to make network calls, even when I have not scrolled at all, let alone reaching the end. This makes the application quite slow. Is there something I'm missing, or is this a library issue? Never had this issue in previous projects I've worked on. The version im using is
flutter_pagewise: ^1.2.3
I've upgraded to the latest version of this package too, still no luck. What am I missing?
I recently had a similar issue (in version 2.0.1), which I solved by making the pageSize bigger.
I think it is a bug of pagewiseListView that happens when the page doesn't fill the given space or fills it too little; however, I have not tried to test that hypothesis.
By the way, when calling your getDetails function, you give the parameter localListSize the value pageIndex and localListStart the value 10 (which seems to be your page size), maybe those two are inverted?

Flutter Admob banner ad is displayed below Status bar

I am trying to achieve a very basic thing: Display Admob Banner ad on top. This works, but the Banner Ad slips inside the status bar and I couldn't find any way to make it display properly. Here is the sample code I am trying:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
// This makes the visual density adapt to the platform that you run
// the app on. For desktop platforms, the controls will be smaller and
// closer together (more dense) than on mobile platforms.
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomeView(),
);
}
}
class HomeView extends StatefulWidget {
#override
_HomeViewState createState() => _HomeViewState();
}
Future<void> _initAdMob() {
return FirebaseAdMob.instance.initialize(appId: AdManager.appId);
}
class _HomeViewState extends State<HomeView> {
// COMPLETE: Add _bannerAd
BannerAd _bannerAd;
// COMPLETE: Implement _loadBannerAd()
void _loadBannerAd() {
_bannerAd
..load()
..show(anchorType: AnchorType.top);
}
#override
void initState() {
_bannerAd = BannerAd(
adUnitId: AdManager.bannerAdUnitId,
size: AdSize.banner,
);
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<void>(
future: _initAdMob(),
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
_loadBannerAd();
return SafeArea(
child: new Column(children: <Widget>[
Text('Sample text')
],),
);
},
);
}
}
This code produces the below output:
which obviously is wrong. The text is right at the place where it should be, however, the Banner ad is slipped inside the status bar, which is not what I intend. Also, since Google doesn't support providing positional arguments for the banner ad, I am completely helpless.
However, the Banner ad in the Google Codelab for Flutter works very well which is way beyond my understanding as a Flutter novice.
Can someone please shed a light and guide me on what's wrong with the sample code?

Flutter Admob check if BannerAd is loaded to lay out UI

I plan to show a banner ad for my app on the top of the screen. When the ad is showing (the user opens the app with an active internet connection) i use Padding Widgets as BottomNavigationItems to place my data below the banner ad. If the user opens the app with no active internet connection (either wifi or mobile data) I get the Ad failed to load : 0 instead of Ad failed to load : 2 which is the official error code for network errors according to https://support.google.com/admob/thread/3494603?hl=en.
Now ive tried to handle if the ad is loaded or not programatically:
void _showBannerAd() async {
_bannerAd = BannerAd(
adUnitId: AdManager.bannerAdUnitId,
size: AdSize.banner,
targetingInfo: _mobileAdTargetingInfo);
bool loaded = await _bannerAd.load();
if (loaded) {
print('success load');
} else {
print('fail load');
}
bool showing =
await _bannerAd.show(anchorOffset: 80.0, anchorType: AnchorType.top);
if (showing) {
print('sucess show');
} else {
print('fail show');
}
}
which i load
#override
void initState() {
_showBannerAd();
super.initState();
}
But even when there is no internet connection the bool values are false although they shouldnt?
Any help please! I want the user to have a good experience - even with 1 Ad in my entire app it looks weird if the ad is not showing and all the data is still padded.
Thanks guys!
Ive finally found an answer to my own question after searching for a while.
Turns out every Admob ad has a listener attribute!
So what i did is
I created a static bool variable in my BottomNavPageState (BottomNavPage is a Statefulwidget) assuming that the user somehow has an internet connection at startup:
static bool adError = false;
Also every BottomNavItemPage is a StatefulWidget. When initialising my routes i just pass the adError variable (at startup) into all the routes which use Ads for now:
List<Widget> _routes = [
TodayPage(),
PlanPage(adError: adError),
TodoPage(adError: adError),
ProgressPage()
];
When intialising my Banner ad i add the Listener to the banner:
void _showBannerAd() async {
_bannerAd = BannerAd(
adUnitId: AdManager.bannerAdUnitId,
size: AdSize.banner,
targetingInfo: _mobileAdTargetingInfo,
listener: (event) {
if (event == MobileAdEvent.failedToLoad) {
setState(() {
adError = true;
_routes = [
TodayPage(),
PlanPage(adError: adError),
TodoPage(adError: adError),
ProgressPage()
];
});
}
});
}
If the ad failes to load i set the State of my BottomNavPageState in which i turn the bool variable to true and reinitialise my routes with the new updated bool.
Also dont forget to add the adError variable to the BottomNavItemPage:
class TodoPage extends StatefulWidget {
final bool adError;
TodoPage({this.adError});
#override
_TodoPageState createState() => _TodoPageState();
}
...
and access the variable to make simple if else check to return the given Padding:
...
Padding(
padding: widget.adError
? EdgeInsets.only(left: 8.0, right: 8.0)
: EdgeInsets.only(top: 60.0, left: 8.0, right: 8.0),
child:
...
And i have to say it works like a charm!

Navigator PushReplacementName gives back button in flutter

This is weird but below code is not working for me. I get a back arrow on the home screen when using below code.
First line below is for dismissing the dialog box. second one is to go to home screen.
Navigator.of(context, rootNavigator: true).pop();
Navigator.of(context).pushReplacementNamed(HomeScreen.id);
This is first time I am facing this kind of situation with pushReplacementNamed. what's going on here ?
It is probably because you have another screen in the stack. When you call pushReplacementNamed, it doesn't replace whole stack with the one you give. Can you try the following code;
// true don't work based on above query condition
Navigator.of(context).pushNamedAndRemoveUntil(HomeScreen.id, (Route<dynamic> route) => true);
// false works
Navigator.of(context).pushNamedAndRemoveUntil(HomeScreen.id, (Route<dynamic> route) => false);
That won't give the required result as you have already even popped the context away before calling another Navigator class. I tried the function Navigator.of(context).pushNamedAndRemoveUntil() but still got my HomeScreen pushed on stack twice with the back button on screen 1. Hence, I finally got this with the inbuilt function Navigator.of(context).popUntil(). You can run this dartpad code https://dartpad.dev/a10ed43452736b5c6b3d1abe6a7eda45 to view the desired effect or view the code below. Below is part of the code from the gist:
...
class ThirdPage extends StatelessWidget{
static const routeName = '/third';
#override
Widget build(BuildContext context){
void _nextPage(){
//Logic here - ***************************
Navigator.of(context).popUntil((Route<dynamic> route) => route.isFirst);
}
return Scaffold(
appBar: AppBar(
title: Text('Third Page'),
),
body: Center(
child: Text('Third Page'),
),
floatingActionButton: FloatingActionButton(
onPressed: _nextPage,
child: Icon(Icons.add),
),
);
}
}
Happy coding D:)

How to properly change video player source at runtime?

I'm building an app that basically is a YouTube clone. I use the official video_player plugin for playback and chewie for controls. I'd like to implement a quality switcher, so the user can decide what quality they want the video to be streamed at
I've built a bottom sheet with switches and I run changeQuality() when the user selects the desired quality. What it should do is simply giving a new source file to the old player and keep playing from where the video left.
This is the video player and chewie player that run on initState():
videoPlayer = VideoPlayerController.network(data == null
? dataAll[indexNo]["video"]["480"]
: data[indexNo]["video"]["480"]);
chewieController = ChewieController(
videoPlayerController: videoPlayer,
aspectRatio: 16 / 9,
autoPlay: true,
allowedScreenSleep: false,
placeholder: data == null
? Image(
image: NetworkImage(dataAll[indexNo]["thumbnail"]),
)
: Image(
image: NetworkImage(data[indexNo]["thumbnail"]),
)
);
And the changeQuality() function:
changeQuality(String newQuality) {
setState(() {
position = videoPlayer.value.position;
chewieController.pause();
videoPlayer = new VideoPlayerController.network(data == null
? dataAll[indexNo]["video"]["$newQuality"]
: data[indexNo]["video"]["$newQuality"]);
chewieController = ChewieController(
videoPlayerController: videoPlayer,
aspectRatio: 16 / 9,
autoPlay: true,
allowedScreenSleep: false,
startAt: position,
);
});
Navigator.of(context).pop();
}
I've also tried disposing the old video player and then setting the new value, but I get an error that variables cannot be used after being disposed.
The switcher works a bit, because it changes the quality around 4 to 5 times and then it runs into an error and won't play anything.
I expand upon this solution for video_player and extend it to also cover chewie.
Key parts of this solution
You need two widgets. MyVideoPlayer that encapsulates video_player and chewie and an outer widget where you react to user input or state changes and swap out MyVideoPlayer with a new one.
This solution roundabouts the whole question in one way. I doesn't solve how to change video of video_player or chewie. Instead it follows the documented principal on how to use chewie for the whole life cycle of a host widget (MyVideoPlayer) and swap that one out to change video url.
You can stuff in more things in the outer widget as you see fit if you don't want to dedicate it just to containing MyVideoPlayer. Ie. if you want a description text adjacent to it based on app state.
Outer Widget
I write with this. but it can be omitted in Dart code.
class QuizVideoPlayer extends StatefulWidget {
#override
_QuizVideoPlayerState createState() => _QuizVideoPlayerState();
}
class _QuizVideoPlayerState extends State<QuizVideoPlayer> {
Word _url;
UniqueKey _urlKey;
// Call this method from button or in reaction to model change etc.
// I call it from Provider.of in didChangeDependencies, but I don't think it is
// a necessary detail of the answer as it depends on how you do state management.
// The key in this solution is that state management occur in the outer widget and
// due to some trigger call _changeUrl() which changes _url and _urlKey which then
// swaps out MyVideoPlayer.
#override
void _changeUrl(String newUrl) async {
this.setState(() {
// Rebuild MyVideoPlayer with a new instance => eventually dispose old controllers
this._url = newUrl;
this._urlKey = UniqueKey();
});
}
#override
Widget build(BuildContext context) {
return
/* ... */
this._url != null
? MyVideoPlayer(
this._url,
this._urlKey,
)
: AspectRatio(
aspectRatio: 3 / 2,
child: Container(color: Colors.black),
)
/* ... */
);
}
}
MyVideoPlayer
I write with this. but it can be omitted in Dart code.
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart';
class MyVideoPlayer extends StatefulWidget {
final String videoUrl;
final UniqueKey newKey;
MyVideoPlayer(this.videoUrl, this.newKey): super(key: newKey); // passing Unique key to dispose old class instance and create new with new data
#override
_MyVideoPlayerState createState() => _MyVideoPlayerState();
}
class _MyVideoPlayerState extends State<MyVideoPlayer> {
VideoPlayerController _controller;
ChewieController _chewie;
#override
void initState() {
this._initControllers(this.widget.videoUrl);
super.initState();
}
void _initControllers(String url) {
this._controller = VideoPlayerController.network(url);
this._chewie = ChewieController(
videoPlayerController: this._controller,
autoPlay: true,
);
}
#override
void dispose() {
this._controller?.dispose();
this._chewie?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Chewie(controller: this._chewie);
}
}

Categories

Resources