using permission handler at main flutter - flutter

I am trying to get user permission at the start of the app inside main but i get and exception.
void main() async {
final permission = Permission.manageExternalStorage.request();
if (await permission.isGranted){
}
Error:
Exception has occurred.
_CastError (Null check operator used on a null value)

Check out the code below, I think it can solve your problem, I tested it and worked for me. Instead of getting the permission in main(), it uses the stateful widget MyApp, tries to get the permission in initState(), uses async function _getPermission() to await the user's response and with setState() updates the permission grant state in _permission member.
In the build method you can decide what your app should do if permission is granted or not. In this example I only output a single message depending on the permission. For example you can return your MaterialApp when granted, and a warning message if not, anything you like.
You might be aware of this, but one more thing: if the user permanently denies permission, this solution will not prompt again, so you have to handle this case, if your app cannot work without this, you can offer the user to go into settings and enable it there.
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _permission = false;
void _getPermission() async {
final grant = await Permission.camera.request().isGranted;
setState(() {
_permission = grant;
});
}
#override
void initState() {
_getPermission();
super.initState();
}
#override
Widget build(BuildContext context) {
String message =
_permission ? 'Permission granted' : 'Permission not granted';
return MaterialApp(home: Scaffold(body: Center(child: Text(message))));
}
}

I don't know which package do you use for permission handling, but if you use this, you have to add request() before isGranted:
if (await Permission.manageExternalStorage.request().isGranted) {
}
Or if you want just to get permission status without asking for it:
var status = await Permission.manageExternalStorage.status;
if (status.isDenied) {
}

Related

In flutter,how to jump to a specified screen(not main screen) after clicking on received background push notification(Foreground mode is ok!)?

As title, In flutter,how to redirect to a specified screen(not main scrren) after clicking on received background push notification?
PS: Foreground mode is ok!
Pls see my code, after I listened background message,then...what should I do next?
The following is my main.dart code:
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import './tabs.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
#pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print("Handling a background message: ${message.messageId}");
print("Handling a background message: ${message.data['id']}");
print("Handling a background message: ${message.data['screen']}");
}
void main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(home: Tabs());
}
}
// return const MaterialApp(home: Tabs());
After clicking on background push notification,jump to a specified screen,not main screen.thanks all.
you can handle the background message after your flutter app has a context.
here the documentation: https://firebase.flutter.dev/docs/messaging/notifications/#handling-interaction
handling interaction from background message
Future<void> setupInteractedMessage() async {
// Get any messages which caused the application to open from
// a terminated state.
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
// If the message also contains a data property with a "type" of "chat",
// navigate to a chat screen
if (initialMessage != null) {
_handleMessage(initialMessage);
}
// Also handle any interaction when the app is in the background via a
// Stream listener
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
}
void _handleMessage(RemoteMessage message) {
if (message.data['type'] == 'chat') {
Navigator.pushNamed(context, '/chat',
arguments: ChatArguments(message),
);
}
}
then call the function inside the initState. if your apps has a SplasScreen put the function there. So after the apps initialize, the function will executed before navigate to the mainscreen.
but if you dont have SplashScree, you can put it on top of all function inside the initState on the MainScreen.
#override
void initState() {
super.initState();
// Run code required to handle interacted messages in an async function
// as initState() must not be async
setupInteractedMessage();
}

setState is not updating the UI even though state is updating

I am trying to wait till amplify configuration is done then load the login screen. Even though state seems to be getting updated I am still getting the loadinscreen. Why is that?
I am not sure if setState is proper method on the init : Importance of Calling SetState inside initState
As per the doc : https://docs.amplify.aws/start/getting-started/integrate/q/integration/flutter/#configure-amplify
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isAmplifyConfigured = false;
late AmplifyAuthCognito auth;
#override
void initState() {
_initializeApp();
super.initState();
}
Future<void> _initializeApp() async {
await _configureAmplify();
setState(() {
_isAmplifyConfigured = true;
});
}
Future<void> _configureAmplify() async {
auth = AmplifyAuthCognito();
try {
await Amplify.addPlugin(auth);
await Amplify.configure(amplifyconfig);
} on AmplifyAlreadyConfiguredException {
print(
'Amplify was already configured. Looks like app restarted on android.');
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: AppRoutes.onGenerateRoute,
initialRoute: _isAmplifyConfigured
? LoginScreen.routeName
: LoadingScreen.routeName,
);
}
}
I think the issue is with you trying to reassign your initialRoute. I'm not super familiar with this property, but given the name I assume this is set once and is not rebuilt, not even when the state changes. It would make sense, also, because the rest of your code sounds like it should work.
Before trying anything else, I'd recommend you move your logic to initialize Amplify to the LoginScreen, and having its body depend on the _isAmplifyConfigured boolean value. So show spinner if it's false, and show Login fields when it's true.
Even better would be to create a HomeScreen, so you can keep this Amplify initialization at the bottom of your app's stack. And then have your HomeScreen either show the Login widgets, the home screen of your app, or a loading state.

How to access BuildContext in main() method of Flutter launcher class?

I need to access BuildContext in main() {} method of the main.dart class to show a popUp dialog in firebase FirebaseMessaging.onMessage.listen method when a new push notification is received, how to access it while there is no BuildContext in main() ?
You can't really.
In your main function in your main.dart, you have to call runApp() to launch your first widget, and within this widget, you will have access to BuildContext since all the stateful/stateless widget will receive the BuildContext through their build method.
For example you may want to do something like this:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MySuperApplication(),
);
}
class MySuperApplicationextends StatefulWidget {
const MySuperApplication({
Key? key,
}) : super(key: key);
#override
_MySuperApplicationState createState() => _MySuperApplicationState();
}
class _MySuperApplicationState extends State<MySuperApplication> {
FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
#override
Widget build(BuildContext context) {
// You have access to your BuildContext here, and you can initialize FirebaseMessaging.
_firebaseMessaging.subscribeToTopic("all");
// return any widget.
return WhatEverWidget()
}
}

How to generate Pre Launch report for Flutter App?

I have a login screen which uses phone authentication for creating account.
I have used Firebase Phone auth for login and also have stored one number for testing purpose.
But don't know how to pass the number and OTP to generate Pre Launch Report.
They are asking for Username, Username Resource ID, Password , Password Resource ID.
Where to find Resource ID for username and password fields in flutter code.
In the Google play console at the bottom of the left
Click on App content
Click on App access
Click on manage
Click on add new instructions
Add your all details here it should be test accounts
Try this :
dependencies:
flutter_runtime_env: ^0.0.4
Example:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_runtime_env/flutter_runtime_env.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isInFirebaseTestLab = false;
#override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
var result = await inFirebaseTestLab();
setState(() {
_isInFirebaseTestLab = result;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('is in FirebaseTest Lab'),
),
body: Center(
child: Text('is in FirebaseTest Lab: $_isInFirebaseTestLab\n'),
),
),
);
}
}

Android Alarm Manager callback doesn't work

Im trying to use plugin Android Alarm Manager for timer and code execution in background, but cant really get it working right. If i set something like "print("v")" as a callback - everything works fine, but when Im trying to do something extra, it just doesn't work.
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:android_alarm_manager/android_alarm_manager.dart';
import 'dart:isolate';
class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
startTimer(sendport) async {
await AndroidAlarmManager.oneShot(
Duration(seconds: 60), 0, timerCallback(sendport),
wakeup: true, exact: true);
}
timerCallback(sendport) {
sendport.send("DONE");
}
class _MyAppState extends State<MyApp> {
ReceivePort receivePort = ReceivePort();
SendPort sendport;
#override
void initState() {
super.initState();
AndroidAlarmManager.initialize();
receivePort.listen((v) {
print(v);
});
}
#override
Widget build(BuildContext context) {
RaisedButton(
onPressed: startTimer(sendport),
child: Text("Start"),
);
}
}
I expect that code to send message after 1 minute, instead im getting message right after execution and get Error
"/flutter (11424): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: 'dart:ui/plugins.dart': Failed assertion: line 62: '': 'callback' must not be null."
In the question code, startTimer() calls timerCallup (sendPort) when setting up the oneShot, and so it's happening immediately.
The timerCallback function required by the oneShot call does not take any parameters. When the timerCallback runs at the alarm time, it runs in the context of an "isolate", which means it is isolated from the rest of your application. Constants seem to be accessible but variable values are not. The timerCallback function can talk to your app via the ports to exchange information and can use limited APIs to talk the the system, but that's about it. Check the docs on isolates for details.
Isolates can find one another by registering and looking up names with IsolateNameServer
You need a name for the port which the main app and the callback isolate can both see, a global constant seems to work ok for that.
The timer callback passed to AlarmManager.oneShot doesn't have a parameter, so it needs to look up the port name to find the SendPort to use when it runs.
Register the port name for the main isolate's SendPort when setting up - you still need the ReceivePort in MyAppState, but you don't need the SendPort.
The code below registers the receive port with a name, the timerCallback looks that name up, and then sends the message to the send port belonging to the receive port. It does feel very clunky.
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:android_alarm_manager/android_alarm_manager.dart';
import 'dart:isolate';
import 'dart:ui';
const String portName = "MyAppPort";
class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
startTimer() async {
await AndroidAlarmManager.oneShot(
Duration(seconds: 60), 0, timerCallback,
wakeup: true, exact: true);
}
timerCallback() {
SendPort sendPort = IsolateNameServer.lookupPortByName(portName);
if (sendPort != null) {
sendport.send("DONE");
}
}
class _MyAppState extends State<MyApp> {
ReceivePort receivePort = ReceivePort();
#override
void initState() {
super.initState();
IsolateNameServer.registerPortName(receivePort.sendPort, portName)
AndroidAlarmManager.initialize();
receivePort.listen((v) {
print(v);
});
}
#override
Widget build(BuildContext context) {
RaisedButton(
onPressed: startTimer(),
child: Text("Start"),
);
}