Flutter CamerPlugin "camera preview" freezed when app goes to background - flutter

Hi iam using flutter cameraPreview to capture photo, the camera preview works fine but when the camera screen goes to background, while on resumed on that screen the camera screen is frezzed, cant able to view cameraPrview onResume.
mycode:
Future<void> _initializeCamera() async {
final cameras = await availableCameras();
final firstCamera = cameras.first;
_controller = CameraController(firstCamera, ResolutionPreset.high);
_initializeControllerFuture = _controller.initialize();
if (!mounted) {
return;
}
setState(() {
isCameraReady = true;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: Stack(children: <Widget>[
FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return CameraPreview(_controller);
} else {
return Center(
child:
CircularProgressIndicator()); // Otherwise, display a loading indicator.
}
},
),],),);
controller is disposed prpoperly.
I want to know why camera preview is disposed while pause.

Issue is solved by initialised cameraPreview onResume
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_controller != null
? _initializeControllerFuture = _controller.initialize()
: null; //on pause camera disposed so we need call again "issue is only for android"
}
}
On resuming to the page, _controller.initialize will call, so that cameraPreview will works fine.
This is due to cameraPreview runs for long on onPause, it will be disposed..onAndroid i think so..

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
// import 'package:flutter/services.dart';
late List<CameraDescription> _cameras;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
_cameras = await availableCameras();
runApp(const CameraApp());
}
/// CameraApp is the Main Application.
class CameraApp extends StatefulWidget {
/// Default Constructor
const CameraApp({Key? key}) : super(key: key);
#override
State<CameraApp> createState() => _CameraAppState();
}
class _CameraAppState extends State<CameraApp> with WidgetsBindingObserver {
late CameraController controller;
// late AppLifecycleState _appLifecycleState;
#override
void initState() {
super.initState();
// _appLifecycleState = AppLifecycleState.resumed;
WidgetsBinding.instance.addObserver(this);
controller = CameraController(_cameras[0], ResolutionPreset.max);
cameraInit();
}
void cameraInit() {
controller.initialize().then((_) {
if (!mounted) {
return;
}
setState(() {});
}).catchError((Object e) {
if (e is CameraException) {
switch (e.code) {
case 'CameraAccessDenied':
// Handle access errors here.
break;
default:
// Handle other errors here.
break;
}
}
});
}
#override
void dispose() {
controller.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
// _appLifecycleState = state;
});
if (state == AppLifecycleState.resumed) {
// App is in the foreground
print('App is in the foreground');
cameraInit();
} else if (state == AppLifecycleState.paused) {
// App is in the background
print('App is in the background');
}
}
#override
Widget build(BuildContext context) {
if (!controller.value.isInitialized) {
return Container();
}
return MaterialApp(
home: CameraPreview(controller),
);
}
}

Related

Flutter: run code like a timer when app is running in background

I have a simple timer which works fine when the app is running in foreground. I can listen to the stream and update the UI. However when the app is in the background it will not continue counting. How can I continue counting when the app is running in the background?
This is my code for the timer:
class SetTimer {
int _seconds = 0;
final _streamController = StreamController<int>.broadcast();
Timer? _timer;
// Getters
Stream<int> get stream => _streamController.stream;
// Setters
void start() {
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
_seconds++;
_updateSeconds();
});
}
void _updateSeconds() {
// stop counting after one hour
if (_seconds < 3600) {
_streamController.sink.add(_seconds);
}
}
}
Try the below code -
I tested it & found it will count the number in the background & there is no problem.
I added a screen record video here, it will help you to understand.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyTest(),
);
}
}
class MyTest extends StatefulWidget {
const MyTest({Key? key}) : super(key: key);
#override
State<MyTest> createState() => _MyTestState();
}
class _MyTestState extends State<MyTest> {
final SetTimer _setTimer = SetTimer();
#override
void initState() {
_setTimer.start();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<int>(
stream: _setTimer.stream,
builder: (
BuildContext context,
AsyncSnapshot<int> snapshot,
) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.active
|| snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return const Text('Error');
} else if (snapshot.hasData) {
return Center(
child: Text(
snapshot.data.toString(),
style: const TextStyle(color: Colors.red, fontSize: 40)
),
);
} else {
return const Text('Empty data');
}
} else {
return Text('State: ${snapshot.connectionState}');
}
},
),
);
}
}
class SetTimer {
int _seconds = 0;
final _streamController = StreamController<int>.broadcast();
Timer? _timer;
// Getters
Stream<int> get stream => _streamController.stream;
// Setters
void start() {
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
_seconds++;
_updateSeconds();
});
}
void _updateSeconds() {
// stop counting after one hour
if (_seconds < 3600) {
_streamController.sink.add(_seconds);
}
}
}
As far as I know, there is no way to run the app in the background, like you want to. I had a similar problem with an app, I developed and didn't found the perfect solution.
As a workaround, I'm using the wakelock package to prevent the mobile device / app from going into standby. Maybe this could also be a solution for you.

Splash Screen if there is no internet connection

Hello I am new in flutter. I created a webapp with webview. Now i want that if there is not internet connection the splash screen should display that there is no internet connection other wise it should load the web page.
in this code in initstate of stateful widget it will check if internet is available using isInternetAvailable then it will show webview else it will show text with Internet not available.
import 'package:flutter/material.dart';
import 'dart:io';
class SplashScreen extends StatefulWidget {
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
bool isInternetAvailable = false;
#override
void initState() {
isInternetConnected().then((value){
setState(() {
isInternetAvailable = value;
});
});
super.initState();
}
Future<bool> isInternetConnected() async {
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print('connected');
return true;
} else {
return false;
}
} on SocketException catch (_) {
print('not connected');
return false;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: isInternetAvailable ? Webview() : Center(
child: Text('Internet not available'),
)
);
}
}

NoSuchMethodError | Camera Package

So I am trying to get my camera preview working on iOS and Android, so that it can take a photo, however, I am receiving this error when trying to access my camera from the bottom navigation.
NoSuchMethodError: The Method '[]' was called on Null.
Receiver: null
Tried calling: [](0)
This is the camera I am using, and according to docs, this should be working correctly.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:stumble/main.dart';
import 'dart:async';
List<CameraDescription> cameras;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
cameras = await availableCameras();
runApp(MyApp());
}
class Camera extends StatefulWidget {
Function setData;
Camera({Key key, this.setData}) : super(key: key);
#override
_CameraScreenState createState() => _CameraScreenState();
}
class _CameraScreenState extends State<Camera> {
CameraController controller;
int selectedCameraIndex;
String imgPath;
var image;
#override
void initState() {
super.initState();
controller = CameraController(cameras[0], ResolutionPreset.max);
controller.initialize().then((_) {
if (!mounted) {
return;
}
setState(() {});
});
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (!controller.value.isInitialized) {
return Container();
}
return MaterialApp(
home: CameraPreview(controller),
);
}
}
I have tried various methods of troubleshooting and to no avail. Am I missing an asset entirely? How can I rectify? This is building on my initial question located here: Camera preview stretched in flutter
Edit:
New code re: answers
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:stumble/main.dart';
import 'dart:async';
List<CameraDescription> cameras;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
cameras = await availableCameras();
runApp(MyApp());
}
class Camera extends StatefulWidget {
Function setData;
Camera({Key key, this.setData}) : super(key: key);
#override
_CameraScreenState createState() => _CameraScreenState();
}
class _CameraScreenState extends State<Camera> {
CameraController controller;
int selectedCameraIndex;
String imgPath;
var image;
#override
void initState() {
super.initState();
if (cameras.isNotEmpty) {
controller = CameraController(cameras[0], ResolutionPreset.max);
controller.initialize().then((_) {
if (!mounted) {
return;
}
setState(() {});
});
}
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (cameras.isEmpty) {
return Center(child: Text('No cameras available'));
}
if (!controller.value.isInitialized) {
return Container();
}
return MaterialApp(
home: CameraPreview(controller),
);
}
}
Edit:
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Camera(setData: (File file) {
_imageArray.add(file);
print("_imageArray-- " + _imageArray.length.toString());
setState(() {});
}),
));
It seems like the error indicates that you do not have any available cameras. You should check if you have available cameras like this:
class _CameraScreenState extends State<Camera> {
CameraController controller;
int selectedCameraIndex;
String imgPath;
var image;
#override
void initState() {
super.initState();
if(cameras.isNotEmpty) {
controller = CameraController(cameras[0], ResolutionPreset.max);
controller.initialize().then((_) {
if (!mounted) {
return;
}
setState(() {});
});
}
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if(cameras.isEmpty) {
return Center(child: Text('No cameras available'));
}
if (!controller.value.isInitialized) {
return Container();
}
return MaterialApp(
home: CameraPreview(controller),
);
}
}
The error indicates that there is no available cameras in your current environment. Are you trying this out on a simulator? If so, that might be the cause.
Try to set a breakpoint to check the value of the cameras variable or print the length of it afterwards like this:
cameras = await availableCameras();
print(cameras.length);
If you get 0 as the length, that means there is no available cameras.
Could you see if this minimal sample works?
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
List<CameraDescription> cameras;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
cameras = await availableCameras();
print(cameras.length); // Confirm here that more than 1 cameras do exist
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Camera(),
);
}
}
class Camera extends StatefulWidget {
Camera({Key key}) : super(key: key);
#override
_CameraScreenState createState() => _CameraScreenState();
}
class _CameraScreenState extends State<Camera> {
CameraController _controller;
#override
void initState() {
super.initState();
print(cameras.length); // Confirm here that cameras.length did not change from above
if (cameras.isNotEmpty) {
_controller = CameraController(cameras[0], ResolutionPreset.max);
_controller.initialize().then((_) {
if (!mounted) {
return;
}
setState(() {});
});
}
}
#override
void dispose() {
_controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (cameras.isEmpty) {
return Center(child: Text('No cameras available'));
}
if (!_controller.value.isInitialized) {
return Container();
}
return CameraPreview(_controller);
}
}

in_app_purchase purchaseUpdatedStream listen event not firing

I want to subscribe to the purchaseUpdatedStream event after my app has initialised as I want to access localization text to display messages to the user if a purchase has failed. However I can't get the listen event to fire unless it's subscribed BEFORE the MaterialApp widget is built.
Working example:
class AppConfig extends InheritedWidget {
AppConfig({
#required this.appName,
#required Widget child,
#required this.prefs,
#required this.devMode
}) : super(child: child);
final String appName;
final SharedPreferences prefs;
final bool devMode;
static AppConfig of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var configuredApp = new AppConfig(
appName: 'app',
child: new MyApp(),
prefs: await SharedPreferences.getInstance(),
devMode: true,
);
InAppPurchaseConnection.enablePendingPurchases();
runApp(configuredApp);
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
StreamSubscription<List<PurchaseDetails>> _subscription;
#override
void initState() {
Stream purchaseUpdated =
InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList, context);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
super.initState();
}
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList, BuildContext context) {
var config = AppConfig.of(context);
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
print('pending');
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
print('error');
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
print('purchased');
}
if (purchaseDetails.pendingCompletePurchase) {
print('complete');
await InAppPurchaseConnection.instance
.completePurchase(purchaseDetails);
}
}
});
}
#override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return MultiProvider(
providers: [
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: config.appName,
home: Scaffold(
body: SomeWidget(); // whack a button in this widget that triggers a product purchase
),
)
);
}
}
Non-working example:
class AppConfig extends InheritedWidget {
AppConfig({
#required this.appName,
#required Widget child,
#required this.prefs,
#required this.devMode
}) : super(child: child);
final String appName;
final SharedPreferences prefs;
final bool devMode;
static AppConfig of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var configuredApp = new AppConfig(
appName: 'app',
child: new AppScaffold(),
prefs: await SharedPreferences.getInstance(),
devMode: true,
);
InAppPurchaseConnection.enablePendingPurchases();
runApp(configuredApp);
}
class AppScaffold extends StatelessWidget {
#override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return MultiProvider(
providers: [
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: config.appName,
home: Scaffold(
body: MyApp()
),
)
);
}
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
StreamSubscription<List<PurchaseDetails>> _subscription;
#override
void initState() {
Stream purchaseUpdated =
InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList, context);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
super.initState();
}
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList, BuildContext context) {
var config = AppConfig.of(context);
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
print('pending');
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
print('error');
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
print('purchased');
}
if (purchaseDetails.pendingCompletePurchase) {
print('complete');
await InAppPurchaseConnection.instance
.completePurchase(purchaseDetails);
}
}
});
}
#override
Widget build(BuildContext context) {
return SomeWidget(); // whack a button in this widget that triggers a product purchase
}
}
Can anyone see if I'm going about this the wrong way and/or explain why this doesn't work?
My own fault - I was using Navigator.pushReplacement(...); elsewhere in the app which was triggering the dispose method on the child widget. Obvious now I think about it.

Is there a way to keep the splash screen as long as we need for the main screen to be ready?

In other words, instead of fixed 15 seconds, is there a way to, to tell it to stop showing when my async function is finished?
If you are using a separate screen for Splash screen, you can simply await the async function you have and the use Navigator.pushReplacement() to open your Main Screen
Example:
class SplashScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return SplashScreenState();
}
}
class SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
handleSplashscreen();
}
void handleSplashscreen() async {
// Wait for async to complete
await someAsyncFunction();
// Open Main page
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => HomePage(user: null)));
}
Future<void> someAsyncFunction() async {
// Do some Network or other stuff
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(child: Text("Loading...")),
);
}
}
If you are simply showing a loader in the same screen while async operation is being done, you can use a FutureBuilder as others suggested.
Or you can conditionally show loader and Main UI using a boolean and setState().
Example:
class MainPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MainPageState();
}
}
class MainPageState extends State<MainPage> {
// Boolean to show/ hide loader
bool isLoading = true;
#override
void initState() {
super.initState();
handleAsync();
}
void handleAsync() async {
// Wait for async to complete
await someAsyncFunction();
// Open Main page
setState(() {
isLoading = false;
});
}
Future<void> someAsyncFunction() async {
// Do some Network or other stuff
}
#override
Widget build(BuildContext context) {
// Using isLoading to check whether async function is complete
return isLoading
? Container(
child: Center(child: Text("Loading...")),
)
: Container(
child: Text("The Actual screen"),
);
}
}
Hope it helps.
Use FutureBuilder from here in the page and show splash screen image when the data is not loaded.