I'm trying to send a message via the IsolateNameServer in flutter.
However, I am unable to receive messages. I've tried checking if the receiver port is listening by calling port.listen() twice, and it says that it is already listening.
Where am I going wrong? [ I am closely following this documentation ]
Here is my main.dart, based on the above doc:
import 'dart:isolate';
import 'dart:math';
import 'dart:ui';
import 'package:android_alarm_manager/android_alarm_manager.dart';
import 'package:flutter/material.dart';
final ReceivePort port = ReceivePort();
const String isolateName = 'isolate';
main() async {
WidgetsFlutterBinding.ensureInitialized();
IsolateNameServer.registerPortWithName(
port.sendPort,
isolateName,
);
runApp(MyApp());
}
class MyApp 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,
accentColor: Colors.pink,
floatingActionButtonTheme:
FloatingActionButtonThemeData(backgroundColor: Colors.pink)),
home: AlarmManagerExample());
}
}
class AlarmManagerExample extends StatefulWidget {
#override
_AlarmManagerExampleState createState() => _AlarmManagerExampleState();
}
class _AlarmManagerExampleState extends State<AlarmManagerExample> {
#override
void initState() {
super.initState();
AndroidAlarmManager.initialize();
port.listen((_) async => await workForMe());
}
workForMe() async {
print("Secondary Function Triggered!");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: RaisedButton(
onPressed: () async {
await AndroidAlarmManager.oneShot(const Duration(seconds: 5),
Random().nextInt(pow(2, 21)), callback);
},
child: Text(
"Alarm Manager",
),
),
),
);
}
static SendPort uiSendPort;
static callback() {
print("Callbacks Triggered!");
// This will be null if we're running in the background.
uiSendPort ??= IsolateNameServer.lookupPortByName(isolateName);
uiSendPort?.send(null);
}
}
The output I am getting is :
Callbacks Triggered!
However, the expected output is:
Callbacks Triggered!
Secondary Function Triggered!
I do believe that error is caused because the workForMe() function isn't static. Moreover, I don't know if callBacks support Future or async.
With that said, try this, as there is no need to make it async.
static void workForMe() {
print("Secondary Function Triggered!");
}
Related
I'm newly learning Serverpod version 0.9.21 as my backend, first I changed the Client address from http://localhost:8080/ to http://10.0.2.2:8080/ so the SocketException error had gone on the emulator and now the sample app (say hello) works correctly. The Problem is my code faces Error 400 with no errorMessage, Also I've added the tables and checked the database, and everything looks fine.
After creating the project, I added Province Protocol inside the test_server package:
class: Province
table: province
fields:
name: String
isEnabled: bool
Then added the province_endpoint:
import 'package:serverpod/serverpod.dart';
import '../generated/protocol.dart';
class ProvinceEndpoint extends Endpoint {
Future<bool> addProvince(Session session, Province province) async {
await Province.insert(session, province);
return true;
}
}
Then run serverpod generate and start the docker and the server as tutorials are mentioning.
And finally the test_flutter main.dart:
import 'package:back_client/back_client.dart';
import 'package:flutter/material.dart';
import 'package:serverpod_flutter/serverpod_flutter.dart';
var client = Client('http://10.0.2.2:8080/')
..connectivityMonitor = FlutterConnectivityMonitor();
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _nameController = TextEditingController(text: "name1");
String result = "here";
addProvince() async {
final province = Province(name: _nameController.text, isEnabled: true);
client.province.addProvince(province).then((value) {
if (value) {
setState(() {
result = "Done adding ${_nameController.text} province";
});
} else {
result = "Nashod!";
}
}).catchError((onError) {
final error = (onError is ServerpodClientException)
? "${onError.message} ${onError.statusCode}"
: "Error ";
setState(() {
result = error;
});
}).whenComplete(() {
print("done");
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
TextField(
controller: _nameController,
),
Text(
result,
style: Theme.of(context).textTheme.headline3,
)
],
),
floatingActionButton: FloatingActionButton(
onPressed: addProvinces,
child: const Icon(Icons.add),
),
);
}
}
I am a noob in Backend stuff, I didn't restart the docker and server after developing the endpoint! As flutter has hot-reload it made me a little bit lazy!
After development, in the terminal that serverpod is running, press Ctrl + C to close its processes, then run docker-compose up --build --detach && dart bin/main.dart
Exception is thrown on second call 'emit' inside 'firstEvent' case. I know why, but I dont know how to make it work. Problem exists when I use event.map.
_AssertionError
emit was called after an event handler completed normally.
This is usually due to an unawaited future in an event handler.
Please make sure to await all asynchronous operations with event handlers
and use emit.isDone after asynchronous operations before calling emit() to
ensure the event handler has not completed.
void main() {
runApp(const MyApp());
}
#freezed
class SampleEvent with _$SampleEvent {
const factory SampleEvent.firstEvent() = FirstEvent;
const factory SampleEvent.secondEvent() = SecondEvent;
}
#freezed
class SampleState with _$SampleState {
const factory SampleState.initialState() = InitialState;
const factory SampleState.firstState() = OneFirstState;
const factory SampleState.secondState() = SecondState;
}
class SampleBloc extends Bloc<SampleEvent, SampleState> {
SampleBloc() : super(const SampleState.initialState()) {
on<SampleEvent>(eventHandler);
}
FutureOr<void> eventHandler(
SampleEvent event,
Emitter<SampleState> emit,
) async {
event.map(
firstEvent: (value) async {
emit(const SampleState.firstState());
await Future.delayed(const Duration(seconds: 2));
// EXCEPTION!!! HERE:
emit(const SampleState.secondState());
},
secondEvent: (value) => null,
);
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocProvider(create: (_) => SampleBloc(), child: const Home()),
);
}
}
class Home extends StatelessWidget {
const Home({super.key});
#override
Widget build(BuildContext context) {
return BlocConsumer<SampleBloc, SampleState>(
builder: (context1, state) {
return TextButton(
onPressed: () => context1.read<SampleBloc>().add(
const SampleEvent.firstEvent(),
),
child: const Text('Launch First Event'),
);
},
listener: (context, state) {},
);
}
}
I have created a small Flutter app, which includes firebase auth and database. I want to deploy it with firebase hosting and did all the necessary steps, but it is still not working. The webside is just empty or MAYBE waiting for something (not sure about that).
firebase login
firebase init
added "site": "xxxxxxxxxxx-d1d99" to my firebase.json file
added js sdks to index.html:
<script src="/__/firebase/8.3.2/firebase-auth.js"></script>
<script src="/__/firebase/8.3.2/firebase-database.js"></script>
firebase deploy
Also I have chosen the 'web' directory instead of 'build/web' as my public directory, because my flutter project creates the flutter create web there.
My main.dart looks like the following. Except from the MaterialApp(), it is completely the same as suggested by flutter/firebase on how to do it.
main.dart
import 'package:faschingsplaner/views/auth/authentication.dart';
import 'package:faschingsplaner/views/auth/root_page.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'views/add_carnival_view/add_carnival_view.dart';
import 'views/home_view/home_view.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// Set default `_initialized` and `_error` state to false
bool _initialized = false;
bool _error = false;
// final Future<FirebaseApp> _fbApp = Firebase.initializeApp();
// Define an async function to initialize FlutterFire
void initializeFlutterFire() async {
try {
await Firebase.initializeApp();
setState(() {
_initialized = true;
});
} catch(e) {
setState(() {
_error = true;
});
}
}
#override
void initState() {
initializeFlutterFire();
super.initState();
}
#override
Widget build(BuildContext context) {
if (_error) {
return Center(
child: Text('Error occured: $_error'),
);
}
if (!_initialized) {
return Center(
child: CircularProgressIndicator(),
);
}
return MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: [GlobalMaterialLocalizations.delegate],
supportedLocales: [
const Locale('de'),
],
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Color.fromRGBO(58, 66, 86, 1.0),
accentColor: Colors.blue),
initialRoute: '/authentication',
routes: {
RootPage.routeName: (context) => RootPage(auth: new Auth()),
HomeView.routeName: (context) => HomeView(),
AddView.routeName: (context) => AddView(),
});
}
}
At this point, I have totally no clue what could be wrong. Please consider that I am pretty new to flutter.
I have a MultiProvider in the main with the following code:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => ReadPreferences(),
),
ChangeNotifierProvider(
create: (context) => ItemsCrud(),
),
],
child: MaterialApp(...
I am using shared preferences to save and updated the last opened list, so the following in my ReadPreferences file:
import 'package:flutter/foundation.dart'; //To use the "ChangeNotifier"
import 'package:shared_preferences/shared_preferences.dart'; //local store
class ReadPreferences extends ChangeNotifier {
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
String openedList = '';
//Constructor method
ReadPreferences() {
getPreferences();
}
void getPreferences() async {
final SharedPreferences prefs = await _prefs;
openedList = prefs.getString('openedList');
}
Future<bool> updateOpenedList({String listTitle}) async {
final SharedPreferences prefs = await _prefs;
bool result = await prefs.setString('openedList', listTitle);
if (result == true) {
openedList = listTitle;
}
notifyListeners();
return result;
}
}
When I'm trying to update the opened list it updates in the shared Preferences file normally but it never listen to the new "openedList" value in my homepage screen.
The code I use in the homepage screen like the following:
child: Text(Provider.of<ReadPreferences>(context).openedList),
I checked many times by printing the new value inside the "ReadPreferences" files, but outside it, it keeps give me the old value not the updated one at all.
I tested with a modified Flutter Counter (default app), everything seams to be working fine. Note that I'm not calling setState() anywhere, so the only refresh is coming from the ReadPreferences class.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ReadPreferences extends ChangeNotifier {
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
String openedList = '';
//Constructor method
ReadPreferences() {
getPreferences();
}
void getPreferences() async {
final SharedPreferences prefs = await _prefs;
openedList = prefs.getString('openedList');
}
Future<bool> updateOpenedList({String listTitle}) async {
final SharedPreferences prefs = await _prefs;
bool result = await prefs.setString('openedList', listTitle);
if (result == true) {
openedList = listTitle;
}
notifyListeners();
return true;
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => ReadPreferences(),
)
],
child: 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> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(Provider.of<ReadPreferences>(context).openedList)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_counter++;
Provider.of<ReadPreferences>(context, listen: false).updateOpenedList(listTitle: (_counter).toString());
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
I finally found the answer, many thanks for #Andrija explanation. What I was doing wrong is to create a new instance from ReadPreferences() then using it for the update method, but the correct approach is to use Provider.of<ReadPreferences>(context, listen: false).updateOpenedList(listTitle: list.title); to use the update method.
For more explanation I'll add #Andrija comment hereafter:-
You are right, you should be using Provider.of. When you add Provider using ChangeNotifierProvider(create: (context) => ReadPreferences(), ) - new instance of ReadPreferences() is created, and it is kept in WidgetTree. This is the instance you want, and you get it by using Provider.of. In your code above, you created a new instance of ReadPreferences - and this is where you added a new value. This new instance has nothing to do with the one that Provider manages, and this new instance has nothing to do with your Widget.
I'm trying to understand RiverPod by migrating my simple FireStore auth Provider example to RiverPod.
This is my AuthenticationService:
import 'package:firebase_auth/firebase_auth.dart';
class AuthenticationService {
final FirebaseAuth _firebaseAuth;
AuthenticationService(this._firebaseAuth);
// with StreamProvider we listen to these changes
Stream<User> get authStateChanges => _firebaseAuth.authStateChanges();
Future<String> signIn({String email, String password}) async {
try {
await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
return 'Signed in';
} on FirebaseAuthException catch (e) {
return e.message;
}
}
Future<String> signUp({String email, String password}) async {
try {
await _firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
return 'Signed up ';
} on FirebaseAuthException catch (e) {
return e.message;
}
}
Future<void> signOut() async {
await _firebaseAuth.signOut();
}
}
In main.dart I made 2 providers so I can use the service and listen to the property inside of the AuthenticationService
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:meditatie_app/authentication_service.dart';
import 'package:meditatie_app/home_page.dart';
import 'package:meditatie_app/signin_page.dart';
import 'package:provider/provider.dart';
Future<void> main() async {
// initalize Firebase and before that we need to initialize the widgets.
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// Normal provider to serve the AuthenticationService in the widgettree
// so the login form can use this provider to use .singIn()
Provider<AuthenticationService>(
create: (_) => AuthenticationService(FirebaseAuth.instance),
),
// also a StreamProvider that serves the AuthenticationSerivce authStateChanges
// this stream is updated by the FirebaseAuth package when users signin or out
// this provider use context.read<AuthenticationService> to find the
// provider dived here above
StreamProvider(
create: (context) =>
context.read<AuthenticationService>().authStateChanges,
)
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: AuthenticationWrapper(),
),
);
}
}
class AuthenticationWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final firebaseUser = context.watch<User>();
if (firebaseUser != null) {
return HomePage();
}
return SignInPage();
}
}
Here the SingIn page:
import 'package:flutter/material.dart';
import 'package:meditatie_app/authentication_service.dart';
import 'package:provider/provider.dart';
class SignInPage extends StatelessWidget {
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
...
RaisedButton(
onPressed: () {
// Sign in code
context.read<AuthenticationService>().signIn(
email: emailController.text.trim(),
password: passwordController.text.trim(),
);
},
...
This works fine with normal Provider, but I can't get it to work with RiverPod
What I did was:
These providers I made global in providers.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/all.dart';
import 'authentication_service.dart';
final authenticationSeriviceProvider =
Provider((ref) => AuthenticationService(FirebaseAuth.instance));
final authStateChangeProvider = StreamProvider.autoDispose<User>((ref) {
return ref
.watch(authenticationSeriviceProvider)
.authStateChanges;
});
Is this correct? The authStateChangeProvider is using the authenticationSeriviceProvider
When is use it like:
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:meditatie_app/home_page.dart';
import 'package:meditatie_app/signin_page.dart';
import 'package:flutter_riverpod/all.dart';
import 'providers.dart';
Future<void> main() async {
// initialize Firebase and before that we need to initialize the widgets.
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
// riverpod needs at toplevel a Provider container
// for storing state of different providers.
ProviderScope(
child: MyApp(),
),
);
}
class MyApp 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: AuthenticationWrapper(),
);
}
}
// Riverpods ConsumerWidget
// which can consume a provider
// rebuild if the value of the provider changes
class AuthenticationWrapper extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final firebaseUser = watch(authStateChangeProvider);
if (firebaseUser != null) {
return HomePage();
}
return SignInPage();
}
}
My 'firebaseUser' is not a User anymore, but an AsyncValue
When I change it to:
class AuthenticationWrapper extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final User firebaseUser = watch(authStateChangeProvider).data?.value;
if (firebaseUser != null) {
return HomePage();
}
return SignInPage();
}
}
It is working, but what am I doing wrong that I now work with AsyncValue
Expanding the previous answer AsyncValue<T> is a sealed class, think of it as StreamBuilder in Flutter having AsyncSnapshot<T> which wraps the value returned from the stream and gives you options to check if its connecting, waiting, withError or withData. In your case
class AuthenticationWrapper extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
return watch(authStateChangeProvider).when(
data: (user) => user == null ? SignInPage() : HomePage(),
loading: () => CircularProgressIndicator(),
error: (err, stack) => SignInPage(),
);
}
}
should handle all the options, now when loading it will show a progress indicator, if there is an error (connection, bad result, etc) it will display the SignInPage, and finally when there is a value you still will need to check if the value returned from the Stream is null (As far as I understand Firebase returns null when there is no user signed in, it doesn't mean the stream is empty) and display the right widget if its null or not.
Just like Provider, after retrieving the user you still have to do the logic with that
See the documentation.
You should use AsyncValue's exposed states to decide what to render. Your code could look something like the following:
class AuthenticationWrapper extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
return watch(authStateChangeProvider).when(
data: (user) => user == null ? SignInPage() : HomePage(),
loading: () => CircularProgressIndicator(),
error: (err, stack) => SignInPage(),
);
}
}
So adjust your return logic to what you'd like for the data, loading, and error states, but this should give you a general idea on how to use AsyncValue.
Another way I found was to use it the way this tutorial did, but with the new riverpod changes:
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_shopping_list/repositories/auth_repository.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
final authControllerProvider = StateNotifierProvider<AuthController, User?>(
(ref) => AuthController(ref.read)..appStarted(),
);
class AuthController extends StateNotifier<User?> {
final Reader _read;
StreamSubscription<User?>? _authStateChangesSubscription;
AuthController(this._read) : super(null) {
_authStateChangesSubscription?.cancel();
_authStateChangesSubscription = _read(authRepositoryProvider)
.authStateChanges
.listen((user) => state = user);
}
#override
void dispose() {
_authStateChangesSubscription?.cancel();
super.dispose();
}
void appStarted() async {
final user = _read(authRepositoryProvider).getCurrentUser();
if (user == null) {
await _read(authRepositoryProvider).signInAnonymously();
}
}
}
And then I used it like this:
#override
Widget build(BuildContext context, WidgetRef ref) {
User? user = ref.watch<User?>(authControllerProvider);
return user != null
? MaterialApp(
title: 'My App',
builder: (context, child) => _Unfocus(child: child!),
home: MainNavigation(),
debugShowCheckedModeBanner: false,
)
: const MaterialApp(
title: 'My App,
home: LoginPage(),
debugShowCheckedModeBanner: false,
);
}