Flutter HTTPS Server on Mobile Phone - flutter

Hi I am trying to run a server locally on the device and access via a webview - but having issue accessing the files - specially te certs on the mobile device. Here is my code:
import 'dart:io';
import 'package:flutter/material.dart' hide Router;
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'package:shelf_router/src/router.dart';
import 'package:webview_flutter/webview_flutter.dart';
Future<void> main() async {
await startServer();
runApp(const MyApp());
}
// Configure routes.
final _router = Router()
..get('/', _rootHandler)
..get('/www/<message>', _echoHandler);
Response _rootHandler(Request req) {
return Response.ok('Hello, World!\n');
}
Response _echoHandler(Request request) {
final message = request.params['message'];
return Response.ok('$message\n');
}
SecurityContext getSecurityContext() {
// Bind with a secure HTTPS connection
final chain =
Platform.script.resolve('certificates/server_chain.pem').toFilePath();
final key =
Platform.script.resolve('certificates/server_key.pem').toFilePath();
return SecurityContext()
..useCertificateChain(chain)
..usePrivateKey(key, password: 'dartdart');
}
startServer() async {
final ip = InternetAddress.anyIPv4;
// Configure a pipeline that logs requests.
final _handler = Pipeline().addMiddleware(logRequests()).addHandler(_router);
// For running in containers, we respect the PORT environment variable.
final port = int.parse(Platform.environment['PORT'] ?? '443');
final server =
await serve(_handler, ip, port, securityContext: getSecurityContext());
print('Server listening on port ${server.port}');
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Server Bind Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Server Bind'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void initState() {
super.initState();
if (Platform.isAndroid) WebView.platform = AndroidWebView();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: WebView(
initialUrl:
'http://127.0.0.1:8888', //'http://${server.address.host}:${server.port}/index.html',
),
),
);
}
}
Here is the error I get:
[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: FileSystemException: Cannot open file, path = '/certificates/server_chain.pem' (OS Error: No such file or directory, errno = 2)
#0 _File.throwIfError (dart:io/file_impl.dart:635:7)
#1 _File.openSync (dart:io/file_impl.dart:479:5)
#2 _File.readAsBytesSync (dart:io/file_impl.dart:539:18)
#3 _SecurityContext.useCertificateChain (dart:io-patch/secure_socket_patch.dart:236:40)
#4 getSecurityContext (package:server_demo/main.dart:34:5)
#5 startServer (package:server_demo/main.dart:49:67)
#6 main (package:server_demo/main.dart:11:9)
#7 _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:145:25)
#8 _rootRun (dart:async/zone.dart:1428:13)
#9 _CustomZone.run (dart:async/zone.dart:1328:19)
#10 _runZoned (dart:async/zone.dart:1863:10)
#11 runZonedGuarded (dart:async/zone.dart:1851:12)
#12 _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:141:5)
#13 _delayEntrypointInv<…>

Assuming your certificate chain is located in assets/localhost.crt, the private key in assets/localhost.key and the assets folder listed under assets in your pubspec.yaml.
You can try with the following code:
void init() async {
var chain =
utf8.encode(await rootBundle.loadString('assets/localhost.crt'));
var key = utf8.encode(await rootBundle.loadString('assets/localhost.key'));
var context = SecurityContext()
..useCertificateChainBytes(chain)
..usePrivateKeyBytes(key);
var server =
await HttpServer.bindSecure(InternetAddress.anyIPv6, 443, context);
await server.forEach((HttpRequest request) {
request.response.statusCode = HttpStatus.ok;
request.response.write('Hello World!');
request.response.close();
});
}
The problem you are having is derived from how a dart script accesses other files vs how flutter packages and access files in the application bundle. Accessing them as assets in the bundle and passing them to the SecurityContext as Bytes did the trick for me.

Related

Unexpected "uncaught" exception passed to `onError` of `runZonedGuarded()` despite `try`/`catch`

Given the following complete minimal example run on Flutter 3.7.3 and Dart 2.19.2:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
WidgetsFlutterBinding.ensureInitialized();
runZonedGuarded(() {
runApp(const MyApp());
}, (error, stack) {
print('onError: $error\n$stack');
});
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override Widget build(BuildContext context) => const MaterialApp(home: MyHomePage());
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final http.Client _httpClient = http.Client();
#override
void initState() {
super.initState();
request().then((value) => print('then')).catchError((error) => print('catchError'));
}
Future<void> request() async {
try {
var request = http.Request('POST', Uri.parse('http://192.168.0.1'));
await _httpClient.send(request);
}
catch (error) {
print('catch: $error');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: const Center(
child: Text('Push the button'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _httpClient.close(), // this leads to the exceptions
child: const Icon(Icons.add),
),
);
}
}
When the "+" button on the UI is pressed, a SocketException is both passed to the onError callback of the runZonedGuarded() and also the catch block of the HTTP request.
The console output is:
Restarted application in 720ms.
flutter: onError: SocketException: Connection attempt cancelled, host: 192.168.0.1, port: 80
#0 _NativeSocket.startConnect (dart:io-patch/socket_patch.dart:694:35)
#1 _RawSocket.startConnect (dart:io-patch/socket_patch.dart:1855:26)
#2 RawSocket.startConnect (dart:io-patch/socket_patch.dart:27:23)
#3 Socket._startConnect (dart:io-patch/socket_patch.dart:2078:22)
#4 Socket.startConnect (dart:io/socket.dart:763:21)
#5 _ConnectionTarget.connect (dart:_http/http_impl.dart:2466:20)
#6 _HttpClient._getConnection.connect (dart:_http/http_impl.dart:2906:12)
#7 _HttpClient._getConnection (dart:_http/http_impl.dart:2911:12)
#8 _HttpClient._openUrl (dart:_http/http_impl.dart:2766:12)
#9 _HttpClient.openUrl (dart:_http/http_impl.dart:2604:7)
#10 IOClient.send
#11 _MyHomePageState.request
#12 _MyHomePageState.initState
#13 StatefulElement._firstBuild
#14 ComponentElement.mount
--- 8< ---
#384 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3953:16)<…>
flutter: catch: Connection attempt cancelled, host: 192.168.0.1, port: 80
flutter: then
What I would like to know is...
Why are two apparently identical exceptions raised rather than one? (The two exception objects have different identity hash codes, but otherwise appear equivalent.)
Why, if the exception passed to onError is considered uncaught, does the stack trace for that very exception clearly show the try/catch block as an ancestor?
How can I catch the uncaught exception? So that it can be dealt with and not be uncaught?
Note: It's intentional for this demonstration that the HTTP request and subsequent client closure results in a SocketException. I'm not asking why this results in a SocketException. I'm asking why there are two exceptions, one of which is considered uncaught, despite the use of try/catch.
Note 2: Run on iOS device.

Flutter/Firebase Connecting to Google Takes Too Long

Trying to connect to Google, which finally worked but after 525 seconds (over 8 min). There has to be reason for this issue, but I can't figure it out.
Thanks in advance for your suggestions!!!!
Here is my main.dart file:
import 'dart:developer';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'auth.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
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 SignIn Too Long',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter SignIn Too Long'),
);
}
}
/// MyHomePage
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late User user;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
floatingActionButton: FloatingActionButton(
onPressed: _googleSignIn,
child: const Icon(Icons.access_time),
),
);
}
///Sign Into Google
void _googleSignIn() {
final stopwatch = Stopwatch();
DateTime dateTime;
dateTime = DateTime.now();
log('SignInWithGoogle: START: ${dateTime.toString()}' );
stopwatch.start();
signInWithGoogle().then((user) => {
log('SignInWithGoogle: END: '
'${dateTime.add(Duration(
milliseconds: stopwatch.elapsedMilliseconds)).toString()}'),
log('SignInWithGoogle: ELAPSED TIME: '
'${Duration(milliseconds: stopwatch.elapsedMilliseconds).inSeconds.
toString()} SECONDS'),
log('SignInWithGoogle: NAME: ${user.displayName!}' ),
log('SignInWithGoogle: EMAIL: ${user.email!}' ),
//sign out
signOutGoogle(),
});
}
}
Here is the auth.dart file, performing the actual connection:
import 'dart:developer';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
//setup firebase and google instances
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
///Async - Sign In with Google
Future<User> signInWithGoogle() async {
try {
//Google authentication
final GoogleSignInAccount? account = await googleSignIn.signIn();
final GoogleSignInAuthentication? authentication =
await account?.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
idToken: authentication?.idToken,
accessToken: authentication?.accessToken);
//firebase authentication
final UserCredential authResult =
await firebaseAuth.signInWithCredential(credential);
final User? user = authResult.user;
assert(await user?.getIdToken() != null);
final User currentUser = firebaseAuth.currentUser!;
assert(currentUser.uid == user?.uid);
return currentUser;
} on Exception catch (e) {
log(e.toString());
rethrow;
}
}
//Sign Out
void signOutGoogle() async {
await firebaseAuth.signOut();
await googleSignIn.signOut();
}
I've added all the needed (at least what I know) build.gradle and pubspec.yaml changes.
Here is the Run console:
Launching lib\main.dart on sdk gphone64 x86 64 in debug mode...
Running Gradle task 'assembleDebug'...
√ Built build\app\outputs\flutter-apk\app-debug.apk.
Installing build\app\outputs\flutter-apk\app.apk...
Debug service listening on ws://127.0.0.1:54147/Xzu_ZHSI-6M=/ws
Syncing files to device sdk gphone64 x86 64...
D/EGL_emulation(14666): app_time_stats: avg=1734.21ms min=4.61ms max=5034.20ms count=3
[log] SignInWithGoogle: START: 2022-10-23 16:11:04.138638
D/CompatibilityChangeReporter(14666): Compat change id reported: 78294732; UID 10212; state: DISABLED
D/EGL_emulation(14666): app_time_stats: avg=3204.18ms min=2.90ms max=44750.13ms count=14
W/System (14666): Ignoring header X-Firebase-Locale because its value was null.
W/System (14666): Ignoring header X-Firebase-Locale because its value was null.
D/FirebaseAuth(14666): Notifying id token listeners about user ( FINmAp3zonflqrkhsXDgBIu2dsA2 ).
D/FirebaseAuth(14666): Notifying auth state listeners about user ( FINmAp3zonflqrkhsXDgBIu2dsA2 ).
[log] SignInWithGoogle: END: 2022-10-23 16:19:49.896638
D/FirebaseAuth(14666): Notifying id token listeners about a sign-out event.
D/FirebaseAuth(14666): Notifying auth state listeners about a sign-out event.
[log] SignInWithGoogle: ELAPSED TIME: 525 SECONDS
[log] SignInWithGoogle: NAME: <valid my name>
[log] SignInWithGoogle: EMAIL: <valid my email>

Binding has not yet been initialized. When Using isloates

** I am creating google map location app
I tried to resolve my self but i am not able to fix this bug
Please help me to fix this bug
i am getting error when i create isolate for get location
I have used packages
google_maps_flutter: ^2.1.8
geocoding: ^2.0.4
geolocator: ^8.2.1
flutter_bloc: ^8.0.1
**
i got error
> Restarted application in 2,738ms.
D/MapsInitializer( 6872): preferredRenderer: null
D/zzca ( 6872): preferredRenderer: null
I/Google Maps Android API( 6872): Google Play services package version: 221215028
I/Google Maps Android API( 6872): Google Play services maps renderer version(legacy): 203115000
7
I/Counters( 6872): exceeded sample count in FrameTime
>
> I/m.example.g_ma( 6872): NativeAlloc concurrent copying GC freed 17476(988KB) AllocSpace objects, 0(0B) LOS objects, 50% free, 7MB/14MB, paused 172us total 133.433ms
10
I/Counters( 6872): exceeded sample count in FrameTime
I/m.example.g_ma( 6872): NativeAlloc concurrent copying GC freed 4201(193KB) AllocSpace objects, 0(0B) LOS objects, 49% free, 7MB/14MB, paused 138us total 149.308ms
7
I/Counters( 6872): exceeded sample count in FrameTime
I/chatty ( 6872): uid=10498(com.example.g_map) androidmapsapi- identical 1 line
3
I/Counters( 6872): exceeded sample count in FrameTime
E/flutter ( 6872): [ERROR:flutter/runtime/dart_isolate.cc(1098)] Unhandled exception:
E/flutter ( 6872): Binding has not yet been initialized.
E/flutter ( 6872): The "instance" getter on the ServicesBinding binding mixin is only available once that binding has been initialized.
E/flutter ( 6872): Typically, this is done by calling "WidgetsFlutterBinding.ensureInitialized()" or "runApp()" (the latter calls the former). Typically this call is done in the "void main()" method. The "ensureInitialized" method is idempotent; calling it multiple times is not harmful. After calling that method, the "instance" getter will return the binding.
E/flutter ( 6872): In a test, one can call "TestWidgetsFlutterBinding.ensureInitialized()" as the first line in the test's "main()" method to initialize the binding.
E/flutter ( 6872): If ServicesBinding is a custom binding mixin, there must also be a custom binding class, like WidgetsFlutterBinding, but that mixes in the selected binding, and that is the class that must be constructed before using the "instance" getter.
E/flutter ( 6872): #0 BindingBase.checkInstance.<anonymous closure>
package:flutter/…/foundation/binding.dart:281
E/flutter ( 6872): #1 BindingBase.checkInstance
package:flutter/…/foundation/binding.dart:363
E/flutter ( 6872): #2 ServicesBinding.instance
package:flutter/…/services/binding.dart:48
E/flutter ( 6872): #3 MethodChannel.binaryMessenger
package:flutter/…/services/platform_channel.dart:132
**My Code is **
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:g_map/cubit/googlemap_cubit.dart';
// import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'screens/home_page.dart';
void main() async{
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return BlocProvider<GooglemapCubit>(
create: (context) => GooglemapCubit(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Map'),
),
);
}
}
import 'dart:developer';
import 'package:geolocator/geolocator.dart';
class GLocationPermission{
Future<bool> checkPermission()async{
bool serviceEnabled ;
LocationPermission permission;
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if(!serviceEnabled){
log("",error: "Location service is not enabled");
}
permission = await Geolocator.checkPermission();
if(permission == LocationPermission.denied){
permission = await Geolocator.requestPermission();
if(permission == LocationPermission.denied){
log("Permission is denied again ");
}
}
if(permission == LocationPermission.deniedForever){
log("",error: "Location permission denied forever");
return false;
}
if(permission == LocationPermission.whileInUse || permission == LocationPermission.always){
return true;
}else{
return false;
}
}
}
import 'dart:developer';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:g_map/isolates/current_location_in_background.dart';
import 'package:g_map/services/location_permission.dart';
import 'package:geocoding/geocoding.dart';
// import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
// import 'package:geolocator/geolocator.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>{
late GoogleMapController _mapController;
final TextEditingController _searchAddressController = TextEditingController();
final _locationPermission = GLocationPermission();
String searchedAddress = "";
late ReceivePort _receivePort;
late Isolate _isolate;
#override
void initState() {
_checkPermission();
super.initState();
}
Future<void> _checkPermission()async{
if(await _locationPermission.checkPermission() == true){
createIsloate();
}
}
Future<void> createIsloate()async{
_receivePort = ReceivePort();
_isolate = await Isolate.spawn(CurrentLocationInBackGround.getLocation, _receivePort.sendPort);
_receivePort.listen((message) {
log("message $message");
// final pos = message as Stream;
// pos.listen((event) {
// log("Event $event");
// });
},
onError: (error){
log("Error $error",error: error);
}
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Stack(
children: [
_googleMap(
inititalCameraPosition: const CameraPosition(
target: LatLng(37.42796133580664, -122.085749655962),
)),
_searchAddressField(context: context)
],
));
}
Widget _googleMap({required CameraPosition inititalCameraPosition}) {
// buildWhen: (previous, current) => current.runtimeType == ,
return GoogleMap(
// minMaxZoomPreference: const MinMaxZoomPreference(1, 20),
zoomControlsEnabled: true,
onMapCreated: _onMapCreated,
myLocationButtonEnabled: true,
initialCameraPosition: inititalCameraPosition,
);
}
Widget _searchAddressField({required BuildContext context}){
return Card(
child: SizedBox(
width: double.infinity,
height: MediaQuery.of(context).size.height*.07,
child: Center(
child: TextField(
onChanged: (value){
// searchedAddress = context.read<GooglemapCubit>().searchAddress(address: value);
},
controller: _searchAddressController,
decoration: InputDecoration(border: InputBorder.none,
suffix: IconButton(onPressed:() => _searchAndNavigate(context: context), icon: const Icon(Icons.search))
),
),
),
),
);
}
Future<void> _searchAndNavigate({required BuildContext context})async{
final location = GeocodingPlatform.instance.locationFromAddress(_searchAddressController.text);
location.then((value){
debugPrint(value[0].latitude.toString());
debugPrint(value[0].longitude.toString());
_mapController.animateCamera(CameraUpdate.newCameraPosition(CameraPosition(
zoom: 12,
target:LatLng(value[0].latitude, value[0].longitude))));
});
}
void _onMapCreated(GoogleMapController controller) {
// _mapController = context.read<GooglemapCubit>().assignControllerOn(controller);
_mapController = controller;
}
}
import 'dart:developer';
import 'dart:isolate';
import 'package:geolocator/geolocator.dart';
class CurrentLocationInBackGround{
static void getLocation(SendPort sendPort)async{
final pos = await Geolocator.getCurrentPosition();
log("pos stream $pos");
sendPort.send(pos);
}
}
I think you tried to run geocoding methods on isolate because it's freezer the main thread
await placemarkFromCoordinates()
or
await locationFromAddress()
will freeze the main thread this bug on there repository
but here is a great solution
link to solution

Flutter | Riverpod & Dart Unhandled Exception: setState() or markNeedsBuild() called during build

Context
I have this AppUser class:
#immutable
class AppUser {
const AppUser({
this.displayName,
this.email,
required this.emailVerified,
this.phoneNumber,
this.photoURL,
required this.uid,
});
AppUser.fromFirebaseUser(User user)
: displayName = user.displayName,
email = user.email,
emailVerified = user.emailVerified,
phoneNumber = user.phoneNumber,
photoURL = user.photoURL,
uid = user.uid;
final String? displayName;
final String? email;
final bool emailVerified;
final String? phoneNumber;
final String? photoURL;
final String uid;
}
In order to manage and use the current user signed in, I have this AppUserController class:
class AppUserController extends StateNotifier<AppUser> {
AppUserController()
: super(
const AppUser(
emailVerified: false,
uid: '',
),
);
Stream<User?> get onAuthStateChanges =>
FirebaseAuth.instance.authStateChanges();
set setAppUser(AppUser appUser) {
state = appUser;
}
Future<void> signOut() async {
await FirebaseAuth.instance.signOut();
}
}
Then, I created 2 providers:
final appUserProvider =
StateNotifierProvider<AppUserController, AppUser>((ref) {
return AppUserController();
});
final appUserStreamProvider = StreamProvider<AppUser?>((ref) {
return ref
.read(appUserProvider.notifier)
.onAuthStateChanges
.map<AppUser?>((user) {
return user != null ? AppUser.fromFirebaseUser(user) : null;
});
});
I need to manage a user’s budgets list. Also, I have to synchronize this list with a Cloud Firestore database, so I created the BudgetsService class:
class BudgetsService {
BudgetsService({
required this.uid,
}) : budgetsRef = FirebaseFirestore.instance
.collection(FirestorePath.budgetsCollection(uid))
.withConverter<Budget>(
fromFirestore: (snapshot, _) => Budget.fromMap(snapshot.data()!),
toFirestore: (budget, _) => budget.toMap(),
);
String uid;
final CollectionReference<Budget> budgetsRef;
Future<void> addUpdate(Budget budget) async {
await budgetsRef.doc(documentPath(budget)).set(budget);
}
Future<void> delete(Budget budget) async {
await budgetsRef.doc(documentPath(budget)).delete();
}
String documentPath(Budget budget) => FirestorePath.budgetDoc(uid, budget);
Future<List<Budget>> getBudgets() async {
final list = await budgetsRef.get();
return list.docs.map((e) => e.data()).toList();
}
}
I use this class through budgetsServiceProvider provider:
final budgetsServiceProvider = Provider<BudgetsService>((ref) {
final AppUser appUser = ref.watch(appUserProvider);
final String uid = appUser.uid;
return BudgetsService(uid: uid);
});
I use BudgetsService class only to interact with the online database. For the rest, I manage the user’s budget list with BudgetsController class:
class BudgetsController extends StateNotifier<List<Budget>> {
BudgetsController() : super(<Budget>[]);
List<String> get names => state.map((b) => b.name).toList();
Future<void> addUpdate(Budget budget, BudgetsService budgetsService) async {
await budgetsService.addUpdate(budget);
if (budgetAlreadyExists(budget)) {
final int index = indexOf(budget);
final List<Budget> newState = [...state];
newState[index] = budget;
state = newState..sort();
} else {
state = [...state, budget]..sort();
}
}
bool budgetAlreadyExists(Budget budget) => names.contains(budget.name);
Future<void> delete(Budget budget, BudgetsService budgetsService) async {
await budgetsService.delete(budget);
final int index = indexOf(budget);
if (index != -1) {
final List<Budget> newState = [...state]
..removeAt(index)
..sort();
state = newState;
}
}
Future<void> retrieveBudgets(BudgetsService budgetsService) async {
state = await budgetsService.getBudgets();
}
int indexOf(Budget budget) => state.indexWhere((b) => b.name == budget.name);
}
I use this class through budgetsProvider provider:
final budgetsProvider =
StateNotifierProvider<BudgetsController, List<Budget>>((ref) {
return BudgetsController();
});
After the user is signed in, my SwitchScreen widget navigates to ConsoleScreen:
class SwitchScreen extends HookWidget {
const SwitchScreen({
Key? key,
}) : super(key: key);
static const route = '/switch';
#override
Widget build(BuildContext context) {
final appUserStream =
useProvider<AsyncValue<AppUser?>>(appUserStreamProvider);
final googleSignIn =
useProvider<GoogleSignInService>(googleSignInServiceProvider);
final appUserController =
useProvider<AppUserController>(appUserProvider.notifier);
return appUserStream.when(
data: (data) {
if (data != null) {
appUserController.setAppUser = data;
final budgetsService = useProvider(budgetsServiceProvider);
return const ConsoleScreen();
} else {
return SignInScreen(
onGooglePressed: googleSignIn.signInWithGoogle,
);
}
},
loading: () {
return const Scaffold(
body: Center(
child: LinearProgressIndicator(),
),
);
},
error: (error, stack) {
return Scaffold(
body: Center(
child: Text('Error: $error'),
),
);
},
);
}
}
Problem
The first time I build the app, I have no problem. But when I perform the hot reload, I get the following error message:
══════ Exception caught by widgets library ═══════════════════════════════════
The following Error was thrown building SwitchScreen(dirty, dependencies: [UncontrolledProviderScope], AsyncValue<AppUser?>.data(value: Instance of 'AppUser'), Instance of 'GoogleSignInService', Instance of 'AppUserController'):
Instance of 'Error'
The relevant error-causing widget was
SwitchScreen
lib\main.dart:67
When the exception was thrown, this was the stack
#0 StateNotifier.state=
package:state_notifier/state_notifier.dart:173
#1 AppUserController.setAppUser=
package:financesmanager/controllers/app_user_controller.dart:42
#2 SwitchScreen.build.<anonymous closure>
package:financesmanager/screens/switch_screen.dart:33
#3 _$AsyncData.when
package:riverpod/src/common.freezed.dart:148
#4 SwitchScreen.build
package:financesmanager/screens/switch_screen.dart:28
...
════════════════════════════════════════════════════════════════════════════════
E/flutter (13932): [ERROR:flutter/shell/common/shell.cc(103)] Dart Unhandled Exception: setState() or markNeedsBuild() called during build.
E/flutter (13932): This UncontrolledProviderScope widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
E/flutter (13932): The widget on which setState() or markNeedsBuild() was called was:
E/flutter (13932): UncontrolledProviderScope
E/flutter (13932): The widget which was currently being built when the offending call was made was:
E/flutter (13932): SwitchScreen, stack trace: #0 Element.markNeedsBuild.<anonymous closure>
package:flutter/…/widgets/framework.dart:4217
E/flutter (13932): #1 Element.markNeedsBuild
package:flutter/…/widgets/framework.dart:4232
E/flutter (13932): #2 ProviderElement._debugMarkWillChange.<anonymous closure>
package:riverpod/…/framework/base_provider.dart:660
E/flutter (13932): #3 ProviderElement._debugMarkWillChange
package:riverpod/…/framework/base_provider.dart:664
E/flutter (13932): #4 ProviderStateBase.exposedValue=.<anonymous closure>
package:riverpod/…/framework/base_provider.dart:900
E/flutter (13932): #5 ProviderStateBase.exposedValue=
package:riverpod/…/framework/base_provider.dart:902
E/flutter (13932): #6 _StateNotifierProviderState._listener
package:riverpod/src/state_notifier_provider.dart:92
E/flutter (13932): #7 StateNotifier.state=
package:state_notifier/state_notifier.dart:162
E/flutter (13932): #8 AppUserController.setAppUser=
package:financesmanager/controllers/app_user_controller.dart:42
E/flutter (13932): #9 SwitchScreen.build.<anonymous closure>
package:financesmanager/screens/switch_screen.dart:33
Question
How can I solve the problem?
Thank you very much!
Update (2021-06-08)
In my main.dart file I have:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
runApp(ProviderScope(child: FMApp()));
}
class FMApp extends HookWidget {
FMApp({
Key? key,
}) : super(key: key);
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
#override
Widget build(BuildContext context) {
final darkTheme = AppTheme.theme(Brightness.dark);
final lightTheme = AppTheme.theme(Brightness.light);
final isLightTheme = useProvider<bool>(themePreferenceProvider);
final theme = isLightTheme ? lightTheme : darkTheme;
return FutureBuilder(
future: _initialization,
builder: (context, snapshot) {
if (snapshot.hasError) {
return FlutterFireInitErrorScreen(
appTitle: 'FM App',
darkTheme: darkTheme,
error: snapshot.error,
theme: theme,
);
}
if (snapshot.connectionState == ConnectionState.done) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'FM App',
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale.fromSubtags(languageCode: 'en'),
Locale.fromSubtags(languageCode: 'es'),
Locale.fromSubtags(languageCode: 'it'),
],
darkTheme: darkTheme,
theme: theme,
initialRoute: SwitchScreen.route,
routes: {
SwitchScreen.route: (context) => const SwitchScreen(),
},
);
}
return FlutterFireInitWaitingScreen(
appTitle: 'FM App',
darkTheme: darkTheme,
theme: theme,
);
},
);
}
}
Possible solution
For now I solved it by replacing, in switch_screen.dart file, this code:
final budgetsService = useProvider(budgetsServiceProvider);
final budgetsController = context.read<BudgetsController>(budgetsProvider.notifier);
budgetsController.retrieveBudgets(budgetsService);
with the following:
final budgetsService = BudgetsService(uid: data.uid);
context
.read(budgetsControllerProvider)
.retrieveBudgets(budgetsService);
What do you think? Is this a good solution? Is there a better one? Thank you!
The interpretation of the error is that two widgets are updating at the same time, probably because they watch the same provider.
When a Child Widget tries to rebuild while its Parent Widget also tries to rebuild, it generates this error. To solve this error, only the Parent Widget needs to rebuild, because the Child Widget will automatically rebuild.
Unfortunately, in the code you provide, I cannot see from where your SwitchScreen is displayed so I cannot tell you where the exact problem could be.

On calling a method from flutter to gRPC service , returning null

I am trying to build a todo app in flutter with gRPC. But when I try to call methods from flutter and printing it its showing null.
server.dart
import 'package:grpc/grpc.dart';
import 'package:grpc/src/server/call.dart';
import 'generated/todo.pbgrpc.dart';
//import 'package:grpc_tutorial/src/generated/todo.pbgrpc.dart';
class TodoService extends TodoServiceBase {
TodoItems todos = new TodoItems(); // TodoItems is generated by Proto
//Create todo
#override
Future<TodoItem> createTodo(ServiceCall call, TodoItem request) async {
// Create a new TodoObject
TodoItem todoItem = new TodoItem();
todoItem.text = request.text;
todoItem.id = todos.$_getList(1).length + 1;
// Add a new todo Item to our list
todos.items.add(todoItem);
// Return a todoItem in accordance with function return typ[e
return todoItem;
}
#override
Future<TodoItems> readTodos(ServiceCall call, voidNoParam request) async {
return todos;
}
#override
Stream<TodoItem> readTodosStream(
ServiceCall call, voidNoParam request) async* {
// Iterate through all of the todos and 'yield' each todo (returns it to the stream)
for (var todo in todos.items) {
yield todo;
}
}
}
/**
* gRPC Server
**/
class TodoServer {
Future<void> main(List<String> args) async {
final server =
Server([TodoService()]); // Create a new server from the TodoService
await server.serve(port: 9000); // Start the server on port 9000
print('Server listening on port ${server.port}...');
}
}
main() {
TodoServer todoServer = new TodoServer();
todoServer.main([]);
}
main.dart
import 'package:flutter/material.dart';
import 'package:grpc/grpc.dart';
import 'generated/todo.pbgrpc.dart';
void main() => 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,
),
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> {
var gprcClient;
var client;
Future<TodoItem> createTodo() async{
final TodoItem todoItem = new TodoItem();
todoItem.text = "Test 1";
// Send a request to the server to make the new item
TodoItem res= await client.createTodo(todoItem);
// Prints the recieved item
print("1");
print(res);
return res;
}
Future<void> readTodo() async{
print("1");
final v = new voidNoParam();
print("hdsbjh");
// Get a list of todos as a future
var todos = await client.readTodos(v);
//print(await client.readTodos(v));
//print(todos);
print(todos.items + "item");
print("hdn");
}
// Future<void> readTodoStream() async{
// print("1");
//
// final v = new voidNoParam();
// client.readTodosStream(v).listen((value) {
// print(value + "val");
// });
// }
#override
void initState(){
super.initState();
gprcClient = ClientChannel("10.0.0.2",
port: 9000,
options: ChannelOptions(
credentials: ChannelCredentials.insecure(),
idleTimeout: Duration(minutes: 1),
));
// client = TodoServiceClient(GrpcClientSingleton().client);
client = TodoClient(gprcClient);
print('initial');
TodoItem res ;
//print(res);
createTodo().then(
(TodoItem val){
setState(() {
res = val;
});
}
);
print(res);
readTodo();
// readTodoStream();
print('final');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
child: Text('DEMO APP'),
));
}
}
Output in console
I/flutter ( 8134): initial
I/flutter ( 8134): null // Here the the val of res should be printed but its showi
I/flutter ( 8134): 1
I/flutter ( 8134): hdsbjh
I/flutter ( 8134): final
You are getting null because your code runs asynchronously. The function createTodo() is marked as async, therefore res gets printed before you even get the result from your server. To know when the future has finished, you can pass a callback-function to .then(...).
To fix your problem you need to move your print-statement inside of your callback-function:
TodoItem res;
createTodo().then(
(TodoItem val){
// This function gets executed when the result is returned from the server
setState(() {
res = val;
print(res); // If you print it here it works
});
}
);
print(res); // <-- This gets called right after createTodo() even it hasn't finished yet