** 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
Related
I am building an app in Flutter. I tried incorporating google maps into the app, but I can't see map on the emulator, as well as real device. I can see the google logo down on the screen and also zoom options can be seen. I have enabled maps sdk for android in google console. Also, I have added meta data required before activity tag.
My dart file displaying maps:
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class Home extends StatefulWidget {
const Home({super.key});
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
static const LatLng _kMapCenter =
LatLng(19.018255973653343, 72.84793849278007);
static const CameraPosition _kInitialPosition =
CameraPosition(target: _kMapCenter, zoom: 11.0, tilt: 0, bearing: 0);
late GoogleMapController _controller;
Future onMapCreated(GoogleMapController controller) async {
_controller = controller;
String value = await DefaultAssetBundle.of(context)
.loadString('assets/map_style.json');
_controller.setMapStyle(value);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Google Maps Demo'),
),
body: GoogleMap(
initialCameraPosition: _kInitialPosition,
onMapCreated: onMapCreated,
),
);
}
}
My debug console:
Restarted application in 1,719ms.
E/Surface (14290): getSlotFromBufferLocked: unknown buffer: 0xe81e0710
I/AssistStructure(14290): Flattened final assist data: 376 bytes, containing 1 windows, 3 views
D/MapsInitializer(14290): preferredRenderer: null
D/zzcb (14290): preferredRenderer: null
I/Google Maps Android API(14290): Google Play services package version: 224617040
I/Google Maps Android API(14290): Google Play services maps renderer version(legacy): 203115000
I/PlatformViewsController(14290): Using hybrid composition for platform view: 0
E/GoogleMapController(14290): Cannot enable MyLocation layer as location permissions are not granted
E/EGL_emulation(14290): eglQueryContext 32c0 EGL_BAD_ATTRIBUTE
E/EGL_emulation(14290): tid 14290: eglQueryContext(2019): error 0x3004 (EGL_BAD_ATTRIBUTE)
D/HostConnection(14290): HostConnection::get() New Host Connection established 0xb73d0620, tid 14683
D/HostConnection(14290): HostComposition ext ANDROID_EMU_CHECKSUM_HELPER_v1 ANDROID_EMU_native_sync_v2 ANDROID_EMU_native_sync_v3 ANDROID_EMU_native_sync_v4 ANDROID_EMU_dma_v1 ANDROID_EMU_direct_mem ANDROID_EMU_host_composition_v1 ANDROID_EMU_host_composition_v2 ANDROID_EMU_YUV_Cache ANDROID_EMU_has_shared_slots_host_memory_allocator ANDROID_EMU_sync_buffer_data ANDROID_EMU_read_color_buffer_dma GL_OES_EGL_image_external_essl3 GL_OES_vertex_array_object GL_KHR_texture_compression_astc_ldr ANDROID_EMU_host_side_tracing ANDROID_EMU_gles_max_version_3_0
D/EGL_emulation(14290): eglCreateContext: 0xa9c85870: maj 1 min 0 rcv 1
2
D/EGL_emulation(14290): eglMakeCurrent: 0xa9c85870: ver 1 0 (tinfo 0xa8476730) (first time)
E/flutter (14290): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Unable to load asset: assets/map_style.json
E/flutter (14290): #0 PlatformAssetBundle.load
package:flutter/…/services/asset_bundle.dart:258
E/flutter (14290): <asynchronous suspension>
E/flutter (14290): #1 AssetBundle.loadString
package:flutter/…/services/asset_bundle.dart:83
E/flutter (14290): <asynchronous suspension>
E/flutter (14290): #2 _HomeState.onMapCreated
package:d4u/…/withcar/home.dart:22
E/flutter (14290): <asynchronous suspension>
E/flutter (14290):
I/com.example.d4(14290): NativeAlloc concurrent copying GC freed 18209(1181KB) AllocSpace objects, 0(0B) LOS objects, 49% free, 4065KB/8131KB, paused 3.627ms total 110.580ms
F/crash_dump32(14756): crash_dump.cpp:474] failed to attach to thread 161: Permission denied
I/com.example.d4(14290): Thread[3,tid=14299,WaitingInMainSignalCatcherLoop,Thread*=0xda884610,peer=0x132c0228,"Signal Catcher"]: reacting to signal 3
I/com.example.d4(14290):
I/com.example.d4(14290): Wrote stack traces to tombstoned
2
E/libprocessgroup(14290): set_timerslack_ns write failed: Operation not permitted
F/crash_dump32(14917): crash_dump.cpp:474] failed to attach to thread 161: Permission denied
E/libprocessgroup(14290): set_timerslack_ns write failed: Operation not permitted
Screenshot of screen displaying maps
I had problem with google maps showing grey screen at first, and I found out that it was because of the emulator doesn't support google maps. But in other emulator also, I can't see maps. I have attached the image of my screen. I also tried in real android device, but it gives the same result.
I believe this post will help you : click here
Here I provided a sample code which actually works on both my emulator and device
class Map extends StatefulWidget {
const Map({Key? key}) : super(key: key);
#override
State<Map> createState() => _MapState();
}
class _MapState extends State<Map> {
static const _initialCameraPosition = CameraPosition(
target: LatLng(13.660397, 100.525939),
zoom: 4,
);
late GoogleMapController _googleMapController;
Marker? _origin;
#override
void dispose() {
_googleMapController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GoogleMap(
myLocationButtonEnabled: false,
initialCameraPosition: _initialCameraPosition,
onMapCreated: (controller) => _googleMapController = controller,
markers: {
if (_origin != null) _origin!,
},
onTap: _addMarker,
),
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
floatingActionButton: FloatingActionButton(
onPressed: () => _googleMapController.animateCamera(
CameraUpdate.newCameraPosition(_initialCameraPosition),
),
foregroundColor: Colors.black,
backgroundColor: Theme.of(context).primaryColor,
child: const Icon(Icons.center_focus_strong),
),
);
}
}
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>
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.
I am trying to create map screen but I found the below error:
======== Exception caught by widgets library =======================================================
The following LateError was thrown building Consumer<MapViewModel>(dirty, dependencies: [_InheritedProviderScope<MapViewModel>]):
LateInitializationError: Field 'initialPosition' has not been initialized.
The relevant error-causing widget was:
Consumer<MapViewModel> file:///Users/mahmoudalharoon/Desktop/Air%20Forces/aireforces/lib/screens/BaseScreen.dart:44:14
When the exception was thrown, this was the stack:
#0 MapViewModel.initialPosition (package:airforces/screens/map/viewmodel/map_viewmodel.dart)
#1 MapScreen.build.<anonymous closure> (package:airforces/screens/map/view/map_screen.dart:19:27)
#2 Consumer.buildWithChild (package:provider/src/consumer.dart:180:19)
#3 SingleChildStatelessWidget.build (package:nested/nested.dart:259:41)
#4 StatelessElement.build (package:flutter/src/widgets/framework.dart:4648:28)
...
====================================================================================================
and this is the below ViewModel I have:
import 'package:airforces/enums/ScreenState.dart';
import 'package:airforces/screens/BaseViewModel.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class MapViewModel extends BaseViewModel {
late LatLng? initialPosition;
late LatLng lastMapPosition = initialPosition!;
void getUserLocation() async {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
List<Placemark> placemarks = await placemarkFromCoordinates(
position.latitude, position.longitude);
initialPosition = LatLng(position.latitude, position.longitude);
print('${placemarks[0].name}');
setState(ViewState.Idle);
}
}
and this is the below map screen I have:
import 'package:airforces/screens/BaseScreen.dart';
import 'package:airforces/screens/map/viewmodel/map_viewmodel.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class MapScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BaseScreen<MapViewModel>(
onFinish: (_) {
},
onModelReady: (mapViewModel) {
mapViewModel.getUserLocation();
},
builder: (context, mapViewModel, child){
return GoogleMap(
initialCameraPosition: CameraPosition(
target: mapViewModel.initialPosition!,
),
myLocationEnabled: true,
);
},
);
}
}
The issue is occurring because initialPosition is null until the late initialization occurs, but it is being converted to a non-nullable type before the late initialization occurs here: mapViewModel.initialPosition!.
You can resolve this one of two ways.
Return a default position if initialPosition is null when accessing it.
target: mapViewModel.initialPosition ?? [defaultPosition];
Display a loading widget until initialPosition is initialized, then display GoogleMap.
Both of these solutions will require a minor state management solution in order to update the widget when initialPosition is finally initialized.
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.