How to run code in the background of a Flutter app? - flutter

I am working on a flutter app. I have a bunch of times of the day and I want to show an alert notification whenever a time comes and also change the UI of the app if it's running.
So I looked for what options I have, I found the followings
Plugins like flutter-workmanager and background_fetch
Native code implementation with Channels
MY questions are:
Which option is the best for my use case?
Is it better implementing a time counter with delay by duration or using an alarm manager.
How can I pass the data (the current time that comes) between the background task and my app so that I can update the UI?
PS: Currently We are interested in a solution that works at least for Android.

I don't think you need background tasks just to show notifications. Using flutter_local_notifications should be enough for your task. You can schedule a notification with this plugin for specific datetime. Inside of the app, you can use Timer to trigger at specific datetime. I'll show you a simple example:
class _HomePageState extends State<HomePage> {
FlutterLocalNotificationsPlugin notifPlugin;
#override
void initState() {
super.initState();
notifPlugin = FlutterLocalNotificationsPlugin();
}
Future<void> scheduleNotification(DateTime dateTime, String title) async {
final now = DateTime.now();
if (dateTime.isBefore(now)) {
// dateTime is past
return;
}
final difference = dateTime.difference(now);
Timer(difference, () {
showDialog(
context: this.context,
builder: (context) {
return AlertDialog(
content: Text(title),
);
}
);
});
await notifPlugin.schedule(title.hashCode, title, title, dateTime, platformChannelSpecifics, payload: 'test');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
child: Container()
),
floatingActionButton: Builder(
builder: (context) {
return FloatingActionButton(
onPressed: () {
final snackbar = SnackBar(content: Text('planned notification'), duration: Duration(seconds: 3));
Scaffold.of(context).showSnackBar(snackbar);
scheduleNotification(DateTime.now().add(Duration(seconds: 2)), 'Hello');
},
);
}
),
);
}
}
But if you need to do some calculations or data fetch then background_fetch is what you need. The only problem with it Apple don't allow background tasks in most cases.

Related

Perform in app force update using current and required build numbers

I want to force update my app.
Here's what I have done so far.
Obtained the current build version of my app using package_info_plus
Obtained the enforced build version which I have stored in the firebase remote config. So I used this package: firebase_remote_config
I then compared the two build numbers to see if the update is needed. What should I do after that?
Here's my code:
void initState(){
super.initState();
checkForUpdate();
_initPackageInfo();
_enforcedVersion();
if(int.parse(_packageInfo.buildNumber) > int.parse(enforcedBuildNumber))
{
//How to force update?
}
}
Future<void> _initPackageInfo() async {
final info = await PackageInfo.fromPlatform();
setState(() {
_packageInfo = info;
});
}
Future<void> _enforcedVersion() async {
final RemoteConfig remoteConfig = RemoteConfig.instance;
await remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(seconds: 10),
minimumFetchInterval: Duration.zero,
));
await remoteConfig.fetchAndActivate();
setState(() {
enforcedBuildNumber = remoteConfig.getString('enforced_build_number');
});
}
You could display a non dismissable dialog which would ask the user to update the application with a redirection button to the device appstore.
By using a package such as url_launcher you can easily do that:
Code Sample
import 'dart:io' show Platform;
import 'package:url_launcher/url_launcher.dart';
// You can show a dialog like this
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => AlertDialog(
title: Text('Please update your app'),
actions: [
TextButton(
onPressed: launchAppStore,
child: Text('Open App Store'),
),
],
),
);
// Method to open the appstore
void launchAppStore() {
/// Depending on where you are putting this method you might need
/// to pass a reference from your _packageInfo.
final appPackageName = _packageInfo.packageName;
if (Platform.isAndroid) {
launch("https://play.google.com/store/apps/details?id=$appPackageName");
} else if (Platform.isIOS) {
launch("market://details?id=$appPackageName");
}
}

authStateChanges only work if in debug mode

I have a flutter app and everything was fine until i want to release it.
I'm using firebase for auth.
I'm using:
firebase_core: ^0.7.0
firebase_auth: ^0.20.0
In debug mode or in release, my firebase auth login works fine. My problem is after that.
I have a decentralized 'listener' to firebaseAuth.authStateChanges. Here is where i control my app authentication. This is my buildSelf$ function in my auth repository(where i build the auth state listener):
ReplayConnectableStream<AuthState> buildSelf$() {
return Rx.concat([
Stream.value(AuthInit()),
firebaseAuth.authStateChanges().switchMap((firebaseUser) {
print('[AuthRepository][buildSelf][authStateChanges] firebaseUser');
print(firebaseUser);
if (firebaseUser == null) {
return Stream.value(Unauthenticated());
}
/* continue function */
return Authenticated(firebaseUser: firebaseUser);
})
]).publishReplay();
}
buildSelf$ is a method for my AuthRepository. And i initialize it on:
AuthRepository._() {
Firebase.initializeApp().then((app) {
_firebaseAuth = FirebaseAuth.instance;
state$ = buildSelf$();
state$.connect();
});
setPackageInfo();
}
static final AuthRepository instance = AuthRepository._();
All this code is inside my AuthRepository.
The problem is:
When i'm running my app in debug mode. Every thing works fine. I can login and my app (my navigator observer uses the auth repository state$) and i'm redirected to home page. [Here a printscreen from terminal in debug mode. success authStateChanges emit
But when i'm running im release mode, my login response shows in my terminal 'success' but my listener does not show state changes. Here a printscreen from terminal in release mode. authStateChanges emit only 1 time(when opening)
I'm realy lost about whats going on. I tried even to call this authStateChanges directly in my app.dart but still the same way(in release, only checking auth state once).
Solution:
After 3 days of headache, i finally found out my problem:
My app does not initialize firebase app on root (main). So i have to initialize it every time i need to use some firebase package.
I believe that every time one app was initialized, the others failed.
I move my firebase.initializeApp() to my main function and remove every other firebase initialize. Then, everything works fine.
This is my main:
void main() async {
/* ... main code ... */
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(App());
}
I did implement a project with the presented methods in release mode in my iPhone but wasn't able to reproduce the problem, the implementation I did to reproduce the project was:
class _TestFirebaseState extends State<TestFirebase> {
bool userLogado = false;
_loginLogout() {
if (FirebaseAuth.instance.currentUser == null) {
FirebaseAuth.instance.signInWithEmailAndPassword(
email: 'teste1#teste.com', password: '123456');
} else {
FirebaseAuth.instance.signOut();
}
}
#override
void initState() {
super.initState();
final stream = Rx.concat(
[
Stream.value('começou'),
FirebaseAuth.instance.authStateChanges().switchMap<String>(
(user) {
debugPrint('user: ' + user.toString());
if (user == null) {
return Stream.value('unauth');
} else {
return Stream.value('auth');
}
},
),
],
).publishReplay();
stream.listen((value) {
debugPrint('value: ' + value);
this.setState(() {
this.userLogado = value == 'auth';
});
});
stream.connect();
}
#override
Widget build(BuildContext context) {
String text = "sair";
if (!userLogado) {
text = "entrar";
}
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
child: Center(
child: RaisedButton(
onPressed: _loginLogout,
child: Text(text),
),
),
),
);
}
}
the only detail is that the main component that loads everything in the app has a future builder that awaits the firebase to init:
Widget build(BuildContext context) {
return MaterialApp(
home: FutureBuilder(
// Initialize FlutterFire:
future: Firebase.initializeApp(),
builder: (context, snapshot) {
// Check for errors
if (snapshot.hasError) {
return Scaffold(
body: Center(
child: Text(
'ocorreu um problema inicializando o aplicativo...',
),
),
);
}
// Once complete, show your application
if (snapshot.connectionState == ConnectionState.done) {
// return LoginPage();
// return VerifySignIn(homePage: Home());
return TestFirebase();
}
// Otherwise, show something whilst waiting for initialization to complete
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
},
),
debugShowCheckedModeBanner: false,
);
}
And another detail is that the firebase configuration is implemented following the flutter tutorial for the platform used.
So in order to evaluate if the app configuration is just fine, I would implement that button app inside the same project so you can evaluate it properly.

Flutter/Dart - Photo changed but not the URL, therefore Provider's Consumer is not Refreshed

I'm using Provider with ChangeNotifier to alert a Consumer once a new photo is uploaded to a server replacing an old photo. The problem is that the URL stays the same as the photo is merely overwritten and keeps the same name. Hence the Consumer doesn't recognize that anything has changed and doesn't refresh the old photo with the new one.
How can I trick the ChangeNotifier into refreshing the URL? Heres' the Consumer in my build;
Consumer<SocialProvider>(
builder: (context, socialProvider, child) {
return Image.network(socialProvider.currentavatar,
);
}),
Here's where the image is chosen in the Gallery and uploaded to overwrite the old image on the server.
GestureDetector(
onTap: () async {
await socialProvider.loadCurrentUserId();
await _listener.openGallery(socialProvider.currentuserid);
String updatedavatar = "http://example.com/same_photo.jpg";
socialProvider.updateAvatar(updatedavatar);
},
And here's the code in the Provider with ChangeNotifier;
Future<void> updateAvatar(String avatar) async {
var box = await Hive.openBox('currentuser');
box.put('currentavatar', avatar);
currentavatar = avatar;
notifyListeners();
}
Any ideas how to trick Consumer into believing the url has changed so that it is refreshed?
I believe the Consumer will rebuild when someone call notifyListeners(). Your issue may be in Image.network(socialProvider.currentavatar,) that flutter reuse same render object when everything is not change. You can try to add key:UniqueLey() to force rebuild the widget every time it build.
Update with some assumption.
Here is the simple code I try to rebuild your environment:
class SimpleProvider with ChangeNotifier {
Future<void> reload() async => notifyListeners();
}
class TestConsumer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider(
create: (context) => SimpleProvider(),
child: Consumer<SimpleProvider>(
builder: (_, simpleProvider, child) {
print('Consumer build');
return child; // Here is your Image?
},
child: Builder(
builder: (context) {
print('Builder build');
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read<SimpleProvider>().reload();
},
),
body: Container(),
);
},
),
),
),
);
}
}
Every time I click the button, SimpleProvider will call the notifyListeners()
(Future is not necessary) and the builder function in Consumer will be called. Here come the questions:
Is the builder really called when you use notifyListeners?
I assume the builder function is called. You can still double check this part. Otherwise you must provide how you call it.
If builder is called, why the Image Widget inside it is not rebuilt?
It is part of flutter design that it reuse as much as it could, for better performance. Sometimes it confuse people that the rebuild is not happen. That's where I guess your problem is here. You can check the basic mechanism from video here:
https://www.youtube.com/watch?v=996ZgFRENMs&ab_channel=Flutter

How Can I PAUSE or RESUME a async task using a button in flutter?

I'm Building An Flutter Application which requires image changes after a period of time. I thought using while loop with a sleep method inside may solve the problem. But It didn't, Image is only getting change after the loop ends. Application UI also gets froze.
So, I used the async Task which I can't control with a Button.
Desired Output: Image should be changed after every 10 seconds and the user can pause or resume method execution.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Test(
),
),
)
);
}}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
int imgnumber=1;
int varToCheckButtonPress = 0;
String BtnTxt = "START";
void inc(){
while(imgnumber<10)
{
print(imgnumber);
await Future.delayed(const Duration(seconds: 10));
setState(() {
imgnumber++;
});
}
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(flex: 1,
child: Container(
child: Image.asset('images/'+imgnumber.toString()+'.png'),
height: 500,
width:500,
color: Colors.green,
),
),
FlatButton(
child: Text(BtnTxt),
onPressed: (){
if (varToCheckButtonPress == 0) {
setState(() {
inc();
BtnTxt = 'PAUSE';
varToCheckButtonPress = 1;
});
} else if (varToCheckButtonPress == 1) {
setState(() {
BtnTxt = 'RESUME';
varToCheckButtonPress = 0;
});
}
},
)
],
);
}
}
I want the user to control the UI with a single button behave as START, PAUSE and RESUME.
Can we Use normal function To implement this functionality?
You should make use of Bloc pattern to manage your states, e.g: StreamBuilder, Providers, and make a timer to push new imageUrl to the sink and let the streamBuilder receive the latest imageUrl.
As for your button, all it controls is the timer. When u hit the play button, new imageUrl will keep pushing to the sink, while you press paused, simply stop the timer, and new image Url will not be pushing new imageUrl to the sink, and of course, reset the timer when you hit the stop button.
Here is a very detail Bloc pattern tutorial you can follow: Medium
The shortcut to achieve this is :
You can probably hold a function in async loop and call setState method on tap to change it's state.
For example :
call this function in desired location
while (_isPaused) {
await Future.delayed(Duration(milliseconds: 500));
}
and then call set state method from onTap, just like this
onTap:(){
setState((){
_isPaused? _isPaused=false: _isPaused=true;
});
}

Flutter Camera Plugin taking dark image bug

I am getting dark images from flutter Camera Plugin.
Camera Preview is showing correctly but after taking the picture it becomes too dark.
I searched and what i found that it's about the FPS and exposure of the camera.
How can I solve this problem?
I need to show camera preview and take pictures in my app.
Please don't tell me to use image_picker package.
Device : Redmi note 4
Android OS : 7.0
Here is the Image
dark image
Here is the code
import 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart' show join;
import 'package:path_provider/path_provider.dart';
Future<void> main() async {
// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();
// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first;
runApp(
MaterialApp(
theme: ThemeData.dark(),
home: TakePictureScreen(
// Pass the appropriate camera to the TakePictureScreen widget.
camera: firstCamera,
),
),
);
}
// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
final CameraDescription camera;
const TakePictureScreen({
Key key,
#required this.camera,
}) : super(key: key);
#override
TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State<TakePictureScreen> {
CameraController _controller;
Future<void> _initializeControllerFuture;
#override
void initState() {
super.initState();
// To display the current output from the Camera,
// create a CameraController.
_controller = CameraController(
// Get a specific camera from the list of available cameras.
widget.camera,
// Define the resolution to use.
ResolutionPreset.medium,
);
// Next, initialize the controller. This returns a Future.
_initializeControllerFuture = _controller.initialize();
}
#override
void dispose() {
// Dispose of the controller when the widget is disposed.
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Take a picture')),
// Wait until the controller is initialized before displaying the
// camera preview. Use a FutureBuilder to display a loading spinner
// until the controller has finished initializing.
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return CameraPreview(_controller);
} else {
// Otherwise, display a loading indicator.
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.camera_alt),
// Provide an onPressed callback.
onPressed: () async {
// Take the Picture in a try / catch block. If anything goes wrong,
// catch the error.
try {
// Ensure that the camera is initialized.
await _initializeControllerFuture;
// Construct the path where the image should be saved using the
// pattern package.
final path = join(
// Store the picture in the temp directory.
// Find the temp directory using the `path_provider` plugin.
(await getTemporaryDirectory()).path,
'${DateTime.now()}.png',
);
// Attempt to take a picture and log where it's been saved.
await _controller.takePicture(path);
// If the picture was taken, display it on a new screen.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DisplayPictureScreen(imagePath: path),
),
);
} catch (e) {
// If an error occurs, log the error to the console.
print(e);
}
},
),
);
}
}
// A widget that displays the picture taken by the user.
class DisplayPictureScreen extends StatelessWidget {
final String imagePath;
const DisplayPictureScreen({Key key, this.imagePath}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Display the Picture')),
// The image is stored as a file on the device. Use the `Image.file`
// constructor with the given path to display the image.
body: Image.file(File(imagePath)),
);
}
}
Just put delay before take picture.
Future.delayed(const Duration(milliseconds: 500), () {
_controller.takePicture(path);
});
I think it's not about a delay, images are dark if exposure is not handled.
Also exposure requires focus pre captures to work and are not handled in official plugin now.
You can use this plugin : CamerAwesome
Official plugin has been quite abandonned. This plugin includes flash, zoom, auto focus, exposure... and no initialisation required.
It uses value notifier to change data directly in preview like this :
// init Notifiers
ValueNotifier<CameraFlashes> _switchFlash = ValueNotifier(CameraFlashes.NONE);
ValueNotifier<Sensors> _sensor = ValueNotifier(Sensors.BACK);
ValueNotifier<Size> _photoSize = ValueNotifier(null);
#override
Widget build(BuildContext context) {
return CameraAwesome(
onPermissionsResult: (bool result) { }
selectDefaultSize: (List<Size> availableSizes) => Size(1920, 1080),
onCameraStarted: () { },
onOrientationChanged: (CameraOrientations newOrientation) { },
zoom: 0.64,
sensor: _sensor,
photoSize: _photoSize,
switchFlashMode: _switchFlash,
orientation: DeviceOrientation.portraitUp,
fitted: true,
);
};
A hack that works for me, with camera plugin: take the picture twice. The first one buys time for the second one to have the proper exposure and focus.
final image = await controller.takePicture(); // is not used
final image2 = await controller.takePicture();