I'm working on an flutter application that should have a common codebase for web and mobile.
My app will have a google map and as far as I've seen there's not a single package to satisfy all platforms.
google_maps_flutter - seems to work only for mobile (IOS / Android)
google_maps_flutter_web - seems to work only for web
So most probably I have to create two separate MapWidgets, one for the web and one for mobile using these separate packages.
For mobile:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class MapSample extends StatefulWidget {
MapSample({Key? key}) : super(key: key);
#override
State<MapSample> createState() => MapSampleState();
}
class MapSampleState extends State<MapSample> {
final Completer<GoogleMapController> _controller = Completer();
static const CameraPosition _kGooglePlex = CameraPosition(
target: LatLng(37.42796133580664, -122.085749655962),
zoom: 14.4746,
);
#override
Widget build(BuildContext context) {
return GoogleMap(
mapType: MapType.hybrid,
initialCameraPosition: _kGooglePlex,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
);
}
}
For the web, it's a bit more complicated, it seems that google_maps_flutter_web isn't actually an usable version, from what I understand, (correct me if I'm wrong) and it actually uses another package that's not developed by the flutter team google_maps 6.0.0.
The objective of google_maps_flutter_web probably is to have the same api as google_maps_flutter (google_maps_flutter_platform_interface) and use it seamlessly, but I couldn't really find an example of how to use it...
How should I go about this? Any change I'm mistaken about google_maps_flutter_web and it actually works? Or I should just try to use google_maps which actually works for the web and just switch widgets based on kIsWeb?
Eventually I found a workaround using google_maps and this answer as inspiration:
Abstract MapWidget
import 'package:client_ojp4danube/map/map_widget_stub.dart'
if (dart.library.html) 'package:client_ojp4danube/map/map_web_widget.dart'
if (dart.library.io) 'package:client_ojp4danube/map/map_widget.dart';
import 'package:flutter/material.dart';
abstract class MapWidget extends StatefulWidget {
factory MapWidget() => getMapWidget();
}
WebMap widget that uses google_maps:
import 'dart:html';
import 'package:client_ojp4danube/map/abstract_map_widget.dart';
import 'package:flutter/cupertino.dart';
import 'package:google_maps/google_maps.dart';
import 'dart:ui' as ui;
Widget getMap() {
String htmlId = "7";
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(htmlId, (int viewId) {
final myLatlng = new LatLng(30.2669444, -97.7427778);
final mapOptions = new MapOptions()
..zoom = 8
..center = new LatLng(30.2669444, -97.7427778);
final elem = DivElement()
..id = htmlId
..style.width = "100%"
..style.height = "100%"
..style.border = 'none';
final map = GMap(elem, mapOptions);
Marker(MarkerOptions()
..position = myLatlng
..map = map
..title = 'Hello World!');
return elem;
});
return HtmlElementView(viewType: htmlId);
}
class WebMap extends StatefulWidget implements MapWidget {
WebMap({Key? key}) : super(key: key);
#override
State<WebMap> createState() => WebMapState();
}
class WebMapState extends State<WebMap> {
#override
Widget build(BuildContext context) {
return getMap();
}
}
MapWidget getMapWidget() {
print("Intra in get map web ");
return WebMap();
}
Mobile Map Widget
import 'dart:async';
import 'package:client_ojp4danube/map/abstract_map_widget.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class MobileMap extends StatefulWidget implements MapWidget {
MobileMap({Key? key}) : super(key: key);
#override
State<MobileMap> createState() => MobileMapState();
}
class MobileMapState extends State<MobileMap> {
final Completer<GoogleMapController> _controller = Completer();
static const CameraPosition _kGooglePlex = CameraPosition(
target: LatLng(37.42796133580664, -122.085749655962),
zoom: 14.4746,
);
#override
Widget build(BuildContext context) {
return GoogleMap(
mapType: MapType.hybrid,
initialCameraPosition: _kGooglePlex,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
);
}
}
MapWidget getMapWidget() {
return MobileMap();
}
getMapWidget - stub
import 'package:client_ojp4danube/map/abstract_map_widget.dart';
// Created because importing dart.html on a mobile app breaks the build
MapWidget getMapWidget() => throw UnsupportedError(
'Cannot create a map without dart:html or google_maps_flutter');
Actually using the abstract widget that will return the widget suited for the platform
import 'package:client_ojp4danube/map/abstract_map_widget.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> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(child: MapWidget()),
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
EDIT: A new official plugin has been released: https://pub.dev/packages/google_maps_flutter_web . It already works with the existing google_maps_flutter plugin, just add your api script in the web/index.html .
As the user exilonX suggested, the current way (Apr '22) to use Google Maps on both Flutter web and mobile, is to load the library dynamically based on the device. However, his answer lacks of some important details. It took me almost 1h to make his work working, therefore I'm sharing here a clearer and more organized solution, hopefully it'll save you some time (I couldn't edit his answer due to long edit queue).
Folder structure:
\widget
\map_widget.dart
\web_map_widget.dart
\mob_map_widget.dart
\map_widget_stub.dart
MapWidget:
In the file map_widget.dart you'll have the abstract MapWidget:
import 'package:flutter/material.dart';
import 'map_widget_stub.dart'
if (dart.library.html) 'web_map_widget.dart'
if (dart.library.io) 'mob_map_widget.dart';
abstract class MapWidget extends StatefulWidget {
factory MapWidget() => getMapWidget();
}
NOTE: the only semicolumn you need on the conditional import is at the end of the second if.
Web MapWidget:
This file will contain the google map shown on web:
import 'dart:html';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:google_maps/google_maps.dart';
import 'map_widget.dart';
MapWidget getMapWidget() => WebMap();
class WebMap extends StatefulWidget implements MapWidget {
WebMap({Key? key}) : super(key: key);
#override
State<WebMap> createState() => WebMapState();
}
class WebMapState extends State<WebMap> {
#override
Widget build(BuildContext context) {
final String htmlId = "map";
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(htmlId, (int viewId) {
final mapOptions = MapOptions()
..zoom = 15.0
..center = LatLng(35.7560423, 139.7803552);
final elem = DivElement()..id = htmlId;
final map = GMap(elem, mapOptions);
map.onCenterChanged.listen((event) {});
map.onDragstart.listen((event) {});
map.onDragend.listen((event) {});
Marker(MarkerOptions()
..position = map.center
..map = map);
return elem;
});
return HtmlElementView(viewType: htmlId);
}
}
Here you can find more details about the web implementation.
Mobile MapWidget:
This file contains the implementation for mobile (android/ios):
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'map_widget.dart';
MapWidget getMapWidget() => MobileMap();
class MobileMap extends StatefulWidget implements MapWidget {
MobileMap({Key? key}) : super(key: key);
#override
State<MobileMap> createState() => MobileMapState();
}
class MobileMapState extends State<MobileMap> {
final Completer<GoogleMapController> _controller = Completer();
static const CameraPosition _kFalentexHouse =
CameraPosition(target: LatLng(44.497858579692135, 11.336362079086408));
#override
Widget build(BuildContext context) {
return GoogleMap(
mapType: MapType.hybrid,
initialCameraPosition: _kFalentexHouse,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
);
}
}
Stub
Finally, you need a stub:
import 'map_widget.dart';
//the error is shown in case of wrong version loaded on wrong platform
MapWidget getMapWidget() => throw UnsupportedError(
'Cannot create a map without dart:html or google_maps_flutter');
Usage
Now you can use the widget MapWidget as a normal widget:
Scaffold(
body: Center(
child: SizedBox(
height: 300,
width: 300,
child: MapWidget(),
),
),
);
NOTE: in order to make the map work you need to set it up with the key. See the official library documentation for mobile and web.
Related
My flutter web feature right now can be able to select files from disk to Uint8List. With Uint8List I can display selected images by widget.
Now I am adding more feature to the web. My motivation is Twitter copy/paste image and post.
The following is my attempt to read image from memory, but it return null to me.
import 'dart:convert';
import 'dart:typed_data';
import 'package:pasteboard/pasteboard.dart';
import 'package:flutter/material.dart';
class Experiment extends StatefulWidget {
static const String routeName = '/experiment';
const Experiment({Key? key}) : super(key: key);
#override
State<Experiment> createState() => _ExperimentState();
}
class _ExperimentState extends State<Experiment> {
List<Uint8List>? _pickedBytes = <Uint8List>[];
void clickPaste() async {
var bytes = await Pasteboard.image;
print("xx");
print(bytes);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: [
Text("Experiment"),
ElevatedButton(
onPressed: clickPaste,
child: Text("BTN"),
)
],
),
)
);
}
}
Question:
How to let flutter web read the image from clipboard to <List>Uint8List?
The working example is here. But it support only Text.
/// Data stored on the system clipboard.
///
/// The system clipboard can contain data of various media types. This data
/// structure currently supports only plain text data, in the [text] property.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Experiment extends StatefulWidget {
static const String routeName = '/experiment';
const Experiment({Key? key}) : super(key: key);
#override
State<Experiment> createState() => _ExperimentState();
}
class _ExperimentState extends State<Experiment> {
Future<void> _clipboardToWidget() async {
var data = await Clipboard.getData('text/plain');
print(data?.text);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: [
Text("Experiment"),
ElevatedButton(
onPressed: _clipboardToWidget,
child: Text("BBTN"),
)
],
),
)
);
}
}
Get the working code sample here
I have an RxList of addOnProducts which contains product and selected attributes.
I am trying to implement the simple multiSelectable grid View, but on clicking the checkBox the selected attribute changes but it is not reflected back to the ui,
If i refresh it will be updated.
I tried Obx()=> (); widget , It is still not updating
My ProductController
class ProductsController extends GetxController {
late Worker worker;
static ProductsController instance = Get.find();
RxList<ProductModel> products = RxList<ProductModel>([]);
RxList<CheckProduct> addOnProducts = <CheckProduct>[].obs;
String collection = "products";
#override
void onReady() {
super.onReady();
products.bindStream(getAllProducts());
worker = once(products, (List<ProductModel> value) {
fillAddOnProducts(value);
}, condition: () => products.isNotEmpty);
}
Stream<List<ProductModel>> getAllProducts() => FirebaseFirestore.instance
.collection(collection)
.snapshots()
.map((query) => query.docs
.map((item) => ProductModel.fromMap(item.data(), item.id))
.toList());
void fillAddOnProducts(List<ProductModel> products) => {
products.forEach((element) {
addOnProducts.add(CheckProduct(product: element, selected: false));
})
};
}
class CheckProduct {
ProductModel product;
bool selected;
CheckProduct(
{required ProductModel this.product, required bool this.selected});
}
My Grid View
class AddOns extends StatelessWidget {
const AddOns({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [],
title: Text("Select Addons"),
),
body: Obx(() => GridView.count(
crossAxisCount: 2,
children: productsController.addOnProducts
.map((element) => ProductWidget(product: element))
.toList(),
)));
}
}
class ProductWidget extends StatelessWidget {
final CheckProduct product;
const ProductWidget({Key? key, required this.product}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow,
margin: EdgeInsets.all(10),
child: Stack(
alignment: Alignment.center,
children: [
Positioned(
top: 4,
left: 4,
child: Checkbox(
value: product.selected,
onChanged: (value) {
print("value of the value is : $value");
print("value of product selected before is: " +
product.selected.toString());
product.selected = value!;
print("value of product selected after is: " +
product.selected.toString());
},
),
),
],
));
}
}
Therefore in the console it is :
I/flutter (20067): value of the value is : true
I/flutter (20067): value of product selected before is: false
I/flutter (20067): value of product selected after is: true
But the checkBox is not updating, it updates only when i refresh, How to overCome this? Adding Obx() to the parent isn't helping..
Find the github link to code below here which has just the question and and the problem faced..
After going through your code. I've implemented the following that will change state without hot reload:
In your main dart you do not need to put your product controller here as you are not using it
main.dart
import 'package:flutter/material.dart';
import 'grid.dart';
void main() {
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 MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: GridSelect(),
);
}
}
Next, I have changed your grid class to generate a list of product widget as the size of the addProduct list length. In my opinion this is a better way to write GridView counts children. Remove obx from your gridview and change your stateful widget to stateless as you are using Getx. It will manage your state even in a stateless widget. Add your product controller here as you will access addProduct list from the controller class.
grid.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:test_project/controllers/productController.dart';
import 'package:test_project/productWidget.dart';
class GridSelect extends StatelessWidget {
final _controller = Get.put(ProductController());
GridSelect({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: GridView.count(
crossAxisCount: 2,
children: List.generate(_controller.addOnProducts.length, (index) => ProductWidget(index: index))
),
);
}
}
In your product controller class, remove the instance as it is not important. That is the only change here:
ProductController.dart
import 'package:get/get.dart';
import 'package:test_project/models/productModel.dart';
class ProductController extends GetxController {
RxList<CheckProduct> addOnProducts = <CheckProduct>[].obs;
#override
void onReady() {
super.onReady();
addOnProducts.add(CheckProduct(product: ProductModel('productOne', 20)));
addOnProducts.add(CheckProduct(product: ProductModel('productTwo', 25)));
addOnProducts.add(CheckProduct(product: ProductModel('productThree', 30)));
addOnProducts.add(CheckProduct(product: ProductModel('productFour', 40)));
}
}
class CheckProduct {
ProductModel product;
RxBool selected = false.obs;
CheckProduct({
required this.product,
});
}
Lastly, your productWidget class needs a required value index. So, the widget knows which index in gridview the user is clicking and use Obx() here in checkbox as you have an observable value selected here. Remember to always use Obx() when you have an obs value. This will update the widget whenever an obs value changes. Here, if you notice we are using Get.find() instead of Put as Get.put is already inside the scope so all you need to do is find the controller that you will use. You can find or put multiple controllers and update values as much as you want.
productWidget.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:test_project/controllers/productController.dart';
class ProductWidget extends StatelessWidget {
final ProductController _controller = Get.find();
final int index;
ProductWidget({Key? key, required this.index}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow,
margin: EdgeInsets.all(20),
child: Stack(
alignment: Alignment.center,
children: [
Positioned(
top: 4,
left: 4,
child: Obx(()=>Checkbox(
value: _controller.addOnProducts[index].selected.value,
onChanged: (value) {
print("value of the value is : $value");
print("value of product selected before is: " +
_controller.addOnProducts[index].selected.toString());
_controller.addOnProducts[index].selected.value = value!;
print("value of product selected after is: " +
_controller.addOnProducts[index].selected.toString());
},
)),
)
],
),
);
}
}
Go through GetX documentation for proper use of GetX. Even though I have 2 apps in Playstore with GetX, I still go through documentation from time to time. They have a clear documentation on how to manage state.
In ProductWidget adding an additional Obx() solved my problem
class ProductWidget extends StatelessWidget {
final CheckProduct product;
const ProductWidget({Key? key, required this.product}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow,
margin: EdgeInsets.all(10),
child: Stack(
alignment: Alignment.center,
children: [
Positioned(
top: 4,
left: 4,
// Even the child needs Obx() ; The parent's Obx() is not reflected here
child: Obx(()=>(Checkbox(
value: product.selected,
onChanged: (value) {
print("value of the value is : $value");
print("value of product selected before is: " +
product.selected.toString());
product.selected = value!;
print("value of product selected after is: " +
product.selected.toString());
},
),))
),
],
));
}
so Iam trying to build flutter maps with the nearest location and I gotten so far hear and the is an error 'inApplicationBloc()' especially in this segment segment
ApplicationBloc() {
setCurrentLocation();
}
and this is the erorr Non-nullable instance field 'currentLocation' must be initialized.
Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'
once I add late initializer I get a red screen error I tryed adding ? to positon and null check in the latitude and longitude but it keeps giving me progress circle indicator and here is my code to get a better understanding
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import '../scr/screens/services/geolocatator_services.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class ApplicationBloc with ChangeNotifier {
final geolocatorService = GeolocatorService();
Position currentLocation; //must change late
ApplicationBloc() {
setCurrentLocation();
}
setCurrentLocation() async {
currentLocation = await geolocatorService.getCurrentLocation();
notifyListeners();
}
}
//second class
import 'package:geolocator/geolocator.dart';
class GeolocatorService {
Future<Position>setCurrentLocation() async {
return await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
}
getCurrentLocation() {}
}
Third class
import 'package:firebase/googlemaps_screens/blocks/app_block.dart';
import 'package:firebase/googlemaps_screens/blocks/app_block.dart';
import 'package:firebase/googlemaps_screens/mainrun.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:provider/provider.dart';
import '../../blocks/app_block.dart';
class HomeScreen extends StatefulWidget {
HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
void initState() {
Position currentLocation;
super.initState();
}
Widget build(BuildContext context) {
final applicationBloc = Provider.of<ApplicationBloc>(context);
return Scaffold(
body: (applicationBloc.currentLocation == null)
? Center(
child: CircularProgressIndicator(),
)
: ListView(
children: [
TextField(
decoration: InputDecoration(hintText: 'Search Location'),
),
Container(
height: 300.0,
child: GoogleMap(
mapType: MapType.normal,
myLocationEnabled: true,
initialCameraPosition: CameraPosition(
target: LatLng(
applicationBloc.currentLocation.latitude,
applicationBloc.currentLocation.longitude,
),
zoom: 14,
),
),
)
],
));
}
}
currentLocation still isn't initialized, in your initState() what you called was the instance of currentLocation which still wasn't initialized anywhere, except in your setCurrentLocation() method, then you have to call the setCurrentLocation() in initState, then you can mark currentLocation as late
Alright so in flutter when you don't want to make a value nullable, because flutter needs to make sure no value is unnecessarily left null, you either have to mark it as a nullable value, orrr mark it as late, if you make it late, then you promise flutter you're going to initialize it later, now if you mark currentLocation as late, it means you promise that you'll give it a value somewhere along the line, and it will never be null before the UI builds and from what I see, it can only get a value after your device gives access to the app to check user location, and then the app grabs it from the device GPS, if you have to do all that, meaning that through that process, the value of currentLocation will be NULL,until it gets location and the builder won't have anything to build the UI with, also, in your HomeScreen build method, you are checking if the currentLocation is null, meaning you expect it to be null at some point, this is why it's advisable to make the currentLocation nullable by adding a question mark, or mark it as late, which means it can't be null, but you promise that it'll get a value later, my advice would be to make it Position? currentLocation; meaning that at some point it will be null....
so either make it
Position? currentLocation;
OR
late Position currentLocation;
but with second option you have to make sure that the app gets the location before the builder builds the UI, meaning you'll eliminate the circularprogressbar,
just do this
Position? currentLocation;
Here so first main.dart pushes me to the loading_screen
i wrote this without Provider, just good ole state management
Geolocator Service
import 'package:geolocator/geolocator.dart';
class GeolocatorService {
Future<Position> setCurrentLocation() async {
await Geolocator.requestPermission();
return await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
}
}
here it requests permission of user to access location, then it gets location if user accepts,
Loading Screen
import 'package:flutter/material.dart';
import 'geolocator_service.dart';
import 'package:geolocator/geolocator.dart';
import 'home_screen.dart';
class LoadingScreen extends StatefulWidget {
const LoadingScreen({Key? key}) : super(key: key);
#override
State<LoadingScreen> createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<LoadingScreen> {
void getLocation() async {
Position position = await GeolocatorService().setCurrentLocation();
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => HomeScreen(
position: position,
),
),
);
}
#override
initState() {
super.initState();
getLocation();
}
#override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
}
in the loading screen I have a method getLocation which gets me the location by calling the setCurrentLocation() method in the geolocator service class,then awaits the response, after it gets the response, it saves it in a position variable, this is the same variable you had a problem initializing, now, here i put the whole thing in initState, so that position must get initialized once the app starts, then pushes me to the HomeScreen()
Home Screen
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:geolocator/geolocator.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key, required this.position}) : super(key: key);
final Position position;
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
const TextField(
decoration: InputDecoration(hintText: 'Search Location'),
),
Container(
height: 300.0,
child: GoogleMap(
mapType: MapType.normal,
myLocationEnabled: true,
initialCameraPosition: CameraPosition(
target: LatLng(
widget.position.latitude,
widget.position.longitude,
),
zoom: 14,
),
),
)
],
));
}
}
finally in HomeScreen, it takes the position that was pushed to this page after it was initialized in the loading screen, and then builds the UI with it
The problem you had was that you weren't calling any methods that initialized the position variable, all you did was create the method that gave it a value, so, the circular progress indicator kept rolling why? because the value was still null, you set the value in a method, but you didn't call the method, the method was just there, dormant, wasn't called anywhere, so until you call the method where you gave position a value, then position won't get a value, and it'll keep being null
IF YOU'RE STILL CONFUSED, ASK ME
I'm trying to have a page in my app display a user's location on a google map, which works in debug mode, but when I run it in release mode it simply loads indefinitely. The strange thing is that if I click on a different page on my app and go back to the map page it will immediately show. Does anyone know what could be going on here?
I also get the following error: [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: MissingPluginException(No implementation found for method camera#animate on channel plugins.flutter.io/google_maps_0)
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:mapapp/provider/location_provider.dart';
import 'package:provider/provider.dart';
class Map extends StatefulWidget {
const Map({Key? key}) : super(key: key);
#override
_State createState() => _State();
}
class _State extends State<Map> {
#override
void initState() {
super.initState();
Provider.of<LocationProvider>(context, listen: false).initalization();
}
//Sets map style once app is resumed to reload the map
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
dynamic controller;
controller.setMapStyle("[]");
}
}
#override
Widget build(BuildContext context) {
return Scaffold(body: googleMapUI());
}
Widget googleMapUI() {
return Consumer<LocationProvider>(builder: (
consumerContext,
model,
child,
) {
while (model.locationPosition != null) {
return Column(
children: [
Expanded(
child: GoogleMap(
mapType: MapType.normal,
padding: const EdgeInsets.only(top: 40.0),
initialCameraPosition:
CameraPosition(target: model.locationPosition!, zoom: 18),
myLocationEnabled: true,
myLocationButtonEnabled: true,
onMapCreated: (GoogleMapController controller) async {
Provider.of<LocationProvider>(context, listen: false);
},
),
)
],
);
}
return const Center(
child: CircularProgressIndicator(),
);
});
}
}
How to set country code selector (default value) based on selected country on the phone
Widget build(BuildContext context) => new Scaffold(
body: Center(
child: CountryCodePicker(
onChanged: print,
// Initial selection and favorite can be one of code ('IT') OR dial_code('+39')
initialSelection: 'IT',
favorite: ['+39','FR'],
// optional. Shows only country name and flag
showCountryOnly: false,
// optional. Shows only country name and flag when popup is closed.
showOnlyCountryWhenClosed: false,
// optional. aligns the flag and the Text left
alignLeft: false,
),
),
);
It looks like you are using the package country_code_picker.
You could get some useful information about the user's default language preference from window.locale from dart:ui, here you can properties like the countryCode and languagueCode, which could be useful for setting a default language. Albeit, it is not certain this is the users preferred language, but an indicator nonetheless.
It appears this package lists supported countries in a List<Map<String,String>> named codes that is exposed in the package. So to be safe the data from window.locale.countryCode should be checked against this list.
Example:
import 'dart:ui';
import 'package:country_code_picker/country_code_picker.dart';
import 'package:country_code_picker/country_codes.dart';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
class CountryPickerWithLocale extends StatefulWidget {
const CountryPickerWithLocale({Key? key, required this.onCountryChanged})
: super(key: key);
final Function(CountryCode) onCountryChanged;
#override
State<CountryPickerWithLocale> createState() =>
_CountryPickerWithLocaleState();
}
class _CountryPickerWithLocaleState extends State<CountryPickerWithLocale> {
late String userCountryCode;
String fallbackCountryCode = "UK";
#override
void initState() {
super.initState();
// Get the language set as default on the users phone
String? systemCountryCode = window.locale.countryCode;
// The package you are using has supported countries defined inside a
// "codes" map.
Map<String,String>? supportedLanguage = codes.firstWhereOrNull((element) {
return element["code"] == systemCountryCode;
});
// Use a fallback if the language is unsupported in the package, or if
// there are some issues with retrieving the country code from the locale.
userCountryCode = supportedLanguage?["code"] ?? fallbackCountryCode;
}
#override
Widget build(BuildContext context) {
return Center(
child: CountryCodePicker(
onChanged: widget.onCountryChanged,
initialSelection: userCountryCode,
showCountryOnly: false,
showOnlyCountryWhenClosed: false,
alignLeft: false,
),
);
}
}
class CountryPickerScreen extends StatelessWidget {
const CountryPickerScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CountryPickerWithLocale(
onCountryChanged: (code) {
print(code.code);
},
),
),
);
}
}
// Some code to run the above example.
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(home: CountryPickerScreen());
}
}
void main() => runApp(const App());