I noticed something strange, quite probably because maybe I don't understand the concept.
I am listening to cloud message from firebase. I have 2 dart files A and B.
A looks like:
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
FirebaseMessaging firebaseMessaging = new FirebaseMessaging();
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
#override
void initState() {
super.initState();
firebaseMessaging.configure(
onLaunch: (Map<String, dynamic> msg) {
print(" onLaunch called $msg");
},
onResume: (Map<String, dynamic> msg) {
print(" onResume called ${(msg)}");
},
onMessage: (Map<String, dynamic> msg) {
//showNotification(msg);
print(" onMessage called in Activity A ${(msg)}");//--!!!!!-------!!!!->notice this
},
);
firebaseMessaging.requestNotificationPermissions(
const IosNotificationSettings(sound: true, alert: true, badge: true));
firebaseMessaging.onIosSettingsRegistered
.listen((IosNotificationSettings setting) {
print('IOS Setting Registered');
});
firebaseMessaging.getToken().then((token) {
print("token: "+token);
});
flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
var android = new AndroidInitializationSettings('#mipmap/ic_launcher');
var iOS = new IOSInitializationSettings();
var initSetttings = new InitializationSettings(android, iOS);
flutterLocalNotificationsPlugin.initialize(initSetttings);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context)=>Sample() ));// calling screen B from action of app bar
},
)
],
),
body: new Container(),
);
}
}
Notice the line where I print in console if a new message is called in "Activity A"..
Now B looks like:
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class Sample extends StatefulWidget {
#override
_SampleState createState() => _SampleState();
}
class _SampleState extends State<Sample> {
#override
void dispose(){
super.dispose();
}
FirebaseMessaging firebaseMessaging1 = new FirebaseMessaging();
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin1;
#override
void initState() {
super.initState();
firebaseMessaging1.configure(
onLaunch: (Map<String, dynamic> msg) {
print(" onLaunch called $msg");
},
onResume: (Map<String, dynamic> msg) {
print(" onResume called ${(msg)}");
},
onMessage: (Map<String, dynamic> msg) {
//showNotification(msg);
print(" onMessage called in Activity B ${(msg)}");//----!!!---!!!!---Notice this
},
);
firebaseMessaging1.requestNotificationPermissions(
const IosNotificationSettings(sound: true, alert: true, badge: true));
firebaseMessaging1.onIosSettingsRegistered
.listen((IosNotificationSettings setting) {
print('IOS Setting Registered');
});
firebaseMessaging1.getToken().then((token) {
print("token: "+token);
});
flutterLocalNotificationsPlugin1 = new FlutterLocalNotificationsPlugin();
var android = new AndroidInitializationSettings('#mipmap/ic_launcher');
var iOS = new IOSInitializationSettings();
var initSetttings = new InitializationSettings(android, iOS);
flutterLocalNotificationsPlugin1.initialize(initSetttings);
print(firebaseMessaging1.toString());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(),
);
}
}
So the aim is simple. When the notification arrives depending on the activity we are in, it should perform different actions.
If in A, print notification arrived in A
If in B, print notification arrived in B
But the problem is, when I switch back to A from B (B was called from A using Navigator push), it still prints Notification arrived in B
Either the dispose doesn't dispose completely or I am missing something
dispose doesn't do anything fancy. It is your job to handle custom dispose behaviors.
More specifically, you have to explicitly clean all the mess you possibly made. In your situation, this translates into unsubscribing to firebase's stream.
This translates into the following:
StreamSubscription streamSubscription;
Stream myStream;
#override
void initState() {
super.initState();
streamSubscription = myStream.listen((foo) {
print(foo);
});
}
#override
void dispose() {
super.dispose();
streamSubscription.cancel();
}
You'll have to do the same thing for all listened streams and similar objects (such as Listenable)
Related
I have a serious problem with my Riverpod. Specifically, I am using StateProvider in Riverpod package. But when I update state, the widget tree does not rebuild. I checked the new state whether is updated by printing out state to see, I see that they are actually updated.
I have some same situations but when I click hot restart/reload page/scroll up,down mouse to change size chrome window, the widget tree rebuild one time.
Please help me and explain everything the most detail and easy to understand. Thank you very much
new state print out but UI not update
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:math';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class Data {
final String data;
Data({required this.data});
}
final helloWorldProvider = StateProvider<Data?>((ref) => Data(data: 'No data'));
class MyApp extends ConsumerStatefulWidget {
const MyApp({super.key});
#override
ConsumerState<MyApp> createState() => _MyAppState();
}
class _MyAppState extends ConsumerState<MyApp> {
#override
void initState() {
// TODO: implement initState4
print("Init state");
super.initState();
// getData();
}
// getData() async {
// // http.Response response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/todos/1'));
// // final title = jsonDecode(response.body)["title"];;
// // ref.read(helloWorldProvider.notifier).update((state) => title);
// SharedPreferences prefs = await SharedPreferences.getInstance();
// prefs.setString('valueTemp', 'newValue');
// String? valueTemp = prefs.getString('valueTemp');
// String value = valueTemp ?? '';
// Data data = Data(data: value);
// ref.read(helloWorldProvider.notifier).update((state) => data);
// print("Đã thực hiện xong");
// }
void _change() {
print("change");
final rawString = generateRandomString(5);
Data data = new Data(data: rawString);
ref.watch(helloWorldProvider.notifier).update((state) => data);
print(ref.read(helloWorldProvider.notifier).state?.data);
}
String generateRandomString(int len) {
var r = Random();
return String.fromCharCodes(List.generate(len, (index) => r.nextInt(33) + 89));
}
#override
Widget build(BuildContext context) {
print('Rebuild');
final data = ref.watch(helloWorldProvider.notifier).state;
final dataText = data?.data ?? 'No text';
print(dataText);
return MaterialApp(
title: 'Google Docs Clone',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: Center(
child: Column(children: [
Text(dataText)
]
)
),
floatingActionButton: FloatingActionButton(
onPressed: _change,
tooltip: 'Change',
child: const Icon(Icons.add),
),
));
}
}
I don't want to use other pattern as Provider, Bloc, StateNotifierProvider, ChangeNotifierProvider... I only want to run StateProvider successfully. I have refered to many articles and stackoverflows answer but I did't found any useful helps to my case.
final data = ref.watch(helloWorldProvider.notifier).state;
is watching the notifier, which rarely changes. You want to watch the state change, as in:
final data = ref.watch(helloWorldProvider);
Fixed, Tested your code.
I recommend this article Flutter Riverpod 2.0: The Ultimate Guide to Advanced Riverpod 😀
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:math';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class Data {
final String data;
Data({required this.data});
}
final helloWorldProvider = StateProvider<Data?>((ref) => Data(data: 'No data'));
class MyApp extends ConsumerStatefulWidget {
const MyApp({super.key});
#override
ConsumerState<MyApp> createState() => _MyAppState();
}
class _MyAppState extends ConsumerState<MyApp> {
#override
void initState() {
// TODO: implement initState4
print("Init state");
super.initState();
// getData();
}
// getData() async {
// // http.Response response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/todos/1'));
// // final title = jsonDecode(response.body)["title"];;
// // ref.read(helloWorldProvider.notifier).update((state) => title);
// SharedPreferences prefs = await SharedPreferences.getInstance();
// prefs.setString('valueTemp', 'newValue');
// String? valueTemp = prefs.getString('valueTemp');
// String value = valueTemp ?? '';
// Data data = Data(data: value);
// ref.read(helloWorldProvider.notifier).update((state) => data);
// print("Đã thực hiện xong");
// }
void _change() {
print("change");
final rawString = generateRandomString(5);
Data data = Data(data: rawString);
ref.read(helloWorldProvider.notifier).update((state) => data);
print(ref.read(helloWorldProvider.notifier).state?.data);
}
String generateRandomString(int len) {
var r = Random();
return String.fromCharCodes(
List.generate(len, (index) => r.nextInt(33) + 89));
}
#override
Widget build(BuildContext context) {
print('Rebuild');
final data = ref.watch(helloWorldProvider)?.data;
final dataText = data ?? 'No text';
print(dataText);
return MaterialApp(
title: 'Google Docs Clone',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: Center(
child: Column(children: [Text(dataText)]),
),
floatingActionButton: FloatingActionButton(
onPressed: _change,
tooltip: 'Change',
child: const Icon(Icons.add),
),
),
);
}
}
Hi all...
I have problem in getting token value
my code is
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage
message) async {
await Firebase.initializeApp();
print('A bg message just showed up : ${message.messageId}');
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(Application());
}
class Application extends StatefulWidget {
#override
State<StatefulWidget> createState() => _Application();
}
class _Application extends State<Application> {
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
String token = '';
#override
void initState() {
super.initState();
_firebaseMessaging.getToken().then((token) {
assert(token != null);
print("token is : " + token!);
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(child: Text('My Token is : $token')),),
);
}
}
I have a small flutter application that uses Firebase Auth to login and then uses bindStream to query a list of documents from Firestore. It works from a fresh start/hot restart, but as soon as I logout I get a firebase/firestore permission error and subsequent login's don't refresh the stream. I thought that a GetxController disposes streams created via bindStream when the view that uses the controller is disposed. In this case, when I logout I pop off all routes via Get.offAll, but it appears the stream is still active and that's when the permissions error happens. But I'm not actually sure what is happening.
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
Get.put(LoginController());
Get.put(AuthController(), permanent: true);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'GetX Firebase Firestore',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SplashScreen(),
);
}
}
auth_controller.dart
class AuthController extends GetxController {
final AuthService _authService = AuthService();
AuthService get authService => _authService;
final LoginController _loginController = Get.find<LoginController>();
LoginController get loginController => _loginController;
Rxn<User> _user = Rxn<User>();
User? get user => _user.value;
#override
void onReady() async {
// bind auth state to _firebaesUser, but also give an initial value
_user = Rxn<User>(_authService.currentUser);
_user.bindStream(_authService.authState);
//run every time auth state changes
ever<User?>(_user, handleAuthChanged);
super.onReady();
}
handleAuthChanged(User? user) {
print("handleAuthChanged - ${user?.uid}");
if (user == null) {
Get.offAll(() => LoginScreen());
} else {
Get.offAll(() => HomeScreen(), binding: HomeBinding());
}
}
}
user_controller.dart
class UserController extends GetxController {
final UserRepository _userRepository = UserRepository();
final repository = UserRepository();
final users = Rx<List<FirestoreUser>>([]);
late Rx<FirestoreUser> _firestoreUser;
FirestoreUser get firestoreUser => _firestoreUser.value;
#override
void onInit() {
super.onInit();
final user = FirebaseAuth.instance.currentUser;
if (user == null) return;
_firestoreUser = Rx<FirestoreUser>(FirestoreUser.fromAuth(user));
// get user data from firestore
_firestoreUser.bindStream(_userRepository.getUserById(user.uid));
// query user collection
getAllUsers();
}
void getAllUsers() {
users.bindStream(repository.getAllUsers());
}
}
home_screen.dart
class HomeScreen extends GetView<UserController> {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("All Sample Users"),
actions: [
IconButton(
onPressed: () => Get.to(ProfileScreen()),
icon: Icon(Icons.person),
),
],
),
body: Obx(
() => ListView.builder(
itemCount: controller.users.value.length,
itemBuilder: (context, index) {
final user = controller.users.value[index];
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(user.photoURL),
),
title: Text(user.displayName),
);
},
),
),
);
}
}
home_binding.dart
class HomeBinding extends Bindings {
#override
void dependencies() {
Get.lazyPut<UserController>(() => UserController(), fenix: true);
}
}
I've the below code that is working fine, reading the csv data from url and printing the output:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:csv/csv.dart';
void fetchUserData() async {
final request = await HttpClient().getUrl(Uri.parse(
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
final response = await request.close();
List<List<dynamic>> rowsAsListOfValues;
await for (final csvString in response.transform(const Utf8Decoder())) {
rowsAsListOfValues =
const CsvToListConverter().convert(csvString);
}
print(rowsAsListOfValues);
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
fetchUserData();
}
#override
Widget build(BuildContext context) { // ... // }
}
Instead of getting the output printed, I need it to be returned into a variable, which I can display in y widget, I tried to do it as below:
Future<List<List<dynamic>>> fetchUserData() async { /// change
final request = await HttpClient().getUrl(Uri.parse(
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
final response = await request.close();
List<List<dynamic>> rowsAsListOfValues;
await for (final csvString in response.transform(const Utf8Decoder())) {
rowsAsListOfValues =
const CsvToListConverter().convert(csvString);
}
return rowsAsListOfValues; /// change
}
class _MyHomePageState extends State<MyHomePage> {
var rowsAsListOfValues; /// new
#override
void initState() {
super.initState();
rowsAsListOfValues = fetchUserData(); /// new
print(rowsAsListOfValues); /// new
}
#override
Widget build(BuildContext context) { // ... // }
}
But I got the output as I/flutter ( 7505): Instance of 'Future<List<List<dynamic>>>'
How can I fix it?
You need to switch from initState to didChangeDependency in this case. Because you need to await some process and you cant wait in initState. However you can wait like this
#override
void didChangeDependencies() async {
super.didChangeDependencies();
rowsAsListOfValues = await fetchUserData();
super.setState(() {}); // to update widget data
/// new
print(rowsAsListOfValues);
}
And this is the result
I/flutter (24313): [[vranches], [Dammam, 2], [Khobar, 3]]
You can wrap your code with Future.delayed() as given below.
#override
void initState() {
super.initState();
Future.delayed(Duration.zero,()async{
rowsAsListOfValues =await fetchUserData();
setState(() {});
print(rowsAsListOfValues); // this return correct value
});
print(rowsAsListOfValues); // this return null
}
Full Code
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:csv/csv.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomePage(title: 'Flutter Demo Home Page'),
);
}
}
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_AppState createState() => _AppState();
}
Future<List<List<dynamic>>> fetchUserData() async {
final request = await HttpClient().getUrl(Uri.parse(
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
final response = await request.close();
List<List<dynamic>> rowsAsListOfValues;
await for (final csvString in response.transform(const Utf8Decoder())) {
rowsAsListOfValues =
const CsvToListConverter().convert(csvString);
}
return rowsAsListOfValues;
}
class _AppState extends State<HomePage> {
var rowsAsListOfValues;
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () async {
rowsAsListOfValues = await fetchUserData();
setState(() {});
print(rowsAsListOfValues);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'$rowsAsListOfValues',
),
],
),
),
);
}
}
The initState method is synchronous, and does not support async. I recommend the use of FutureBuilder, but you can also move the code to an async function.
FutureBuilder
import 'package:flutter/material.dart' show
Widget, FutureBuilder, AsyncSnapshot
;
class _MyHomePageState extends State<MyHomePage> {
static Future<void> fetchUserData() {
return Future().delayed(
Duration(seconds: 10),
() => 'loaded'
);
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([
fetchUserData()
]),
builder: (
BuildContext context,
AsyncSnapshot snapshot
) {
if (snapshot.hasData) {
return Text(snapshot.data);
}
return Text('loading...');
}
);
}
}
Async function
#override
void initState () {
super.initState();
(() async {
rowsAsListOfValues = await fetchUserData();
print(rowsAsListOfValues);
})();
}
OR
#override
void initState() {
super.initState();
initLoad();
}
void initLoad() async {
rowsAsListOfValues = await fetchUserData();
print(rowsAsListOfValues);
}
I feel more relaxed when using then() with async functions. You can try this:
fetchUserData().then((value) {
setState(() {
rowsAsListOfValues = value;
});
});
Or you can use await like this.
#override
void initState() async {
super.initState();
rowsAsListOfValues = await fetchUserData();
print(rowsAsListOfValues);
}
I want to make requests in the background every 30 seconds and foreground every 5 seconds, how to use Stream in this case?
here is my code:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
// background proccess off
setPost(30);
} else {
// background proccess on
setPost(5);
}
}
Stream<int> setPost(int seconds) async* {
yield* Stream.periodic(Duration(seconds: seconds), (int i) {
post();
return i;
});
}
void post() async {
try {
var header = {
"Content-Type": "application/json",
};
var response = await http.post(url, headers: header);
if (response.statusCode == 200) {
print(response.body);
} else {
print(response.body);
}
} on SocketException catch (_) {
print('not connected');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(),
);
}
}
Just use Timer.pediodic:
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
Timer timer;
bool waitingForResponse = false;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this); // Adding an observer
setTimer(false); // Setting a timer on init
}
#override
void dispose() {
timer?.cancel(); // Cancelling a timer on dispose
WidgetsBinding.instance.removeObserver(this); // Removing an observer
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
setTimer(state != AppLifecycleState.resumed);
}
void setTimer(bool isBackground) {
int delaySeconds = isBackground ? 5 : 3;
// Cancelling previous timer, if there was one, and creating a new one
timer?.cancel();
timer = Timer.periodic(Duration(seconds: delaySeconds), (t) async {
// Not sending a request, if waiting for response
if (!waitingForResponse) {
waitingForResponse = true;
await post();
waitingForResponse = false;
}
});
}
// Async method returns Future<> object
Future<void> post() async {
...
}
...