Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 3 years ago.
Improve this question
I am porting over a flutter mobile project to flutter web and was wondering how to use the google maps library with Flutter Web.
Please see the other answer. It is easier than this one!
I was able to get a workable solution, but it isn't pretty. Below is the implementation. If I get some time and a legit port doesn't come a long I will post an example repo.
import 'dart:html';
import 'package:flutter_web/material.dart';
import 'package:lift_ai/base/screen_state.dart';
import 'package:lift_ai/feature/property_common/property_contract.dart';
import 'package:lift_ai/feature/property_common/property_presenter_impl.dart';
import 'package:lift_ai/model/car_status.dart';
import 'package:lift_ai/model/property.dart';
import 'package:flutter_web_ui/ui.dart' as ui;
import 'package:lift_ai/util/widget_util.dart';
class PropertyMapPage extends StatefulWidget {
final CarStatus carStatus;
PropertyMapPage(Key key, this.carStatus) : super(key: key);
#override
_PropertyMapPageState createState() => _PropertyMapPageState(carStatus);
}
class _PropertyMapPageState extends State<PropertyMapPage>
implements PropertyListView {
PropertyPresenter _propertyListPresenter;
List<Property> properties = [];
CarStatus carStatus;
String createdViewId = 'hello-world-html';
bool inProgress = true;
_PropertyMapPageState(this.carStatus) {
_propertyListPresenter = PropertyPresenterImpl(this);
}
#override
void initState() {
super.initState();
_propertyListPresenter.getProperties(carStatus, "");
}
#override
void dispose() {
super.dispose();
_propertyListPresenter = null;
}
#override
Widget build(BuildContext context) {
print("Creating html view");
if (inProgress) {
return Center(child: CircularProgressIndicator());
}
return Row(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width - 400,
child: HtmlView(
viewType: createdViewId,
)),
Container(
width: 400,
child: properties.isEmpty
? WidgetUtil.getEmptyPropertiesView(context)
: ListView.builder(
padding: EdgeInsets.all(8.0),
itemCount: properties.length,
itemBuilder: (_, index) {
return WidgetUtil.buildListRow(
context, _propertyListPresenter, properties[index]);
},
),
),
],
);
}
#override
void showProperties(List<Property> properties) {
String markers = "";
for (Property property in properties) {
String marker =
"var marker = new google.maps.Marker({position: new google.maps.LatLng(${property.lat}, ${property.lng}), map: map, title: 'Hello ${property.id}!'});\n";
markers += marker;
}
String createdViewUpdate = DateTime.now().toString();
rootBundle.loadString('map.html').then((value) {
value = value.replaceAll(new RegExp(r'markers'), markers);
ui.platformViewRegistry.registerViewFactory(
createdViewId,
(int viewId) => IFrameElement()
..width = (MediaQuery.of(context).size.width - 400).toString()
..height = MediaQuery.of(context).size.height.toString()
..srcdoc = value
..style.border = 'none');
});
setState(() {
inProgress = false;
this.createdViewId = createdViewUpdate;
this.properties = properties;
});
}
#override
void updateScreenState(ScreenState screenState) { }
#override
void showException(String string) {
// TODO: implement showException
}
}
map.html
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<title>Simple Markers</title>
<style>
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
function initMap() {
var myLatLng = {lat: 41.850033, lng: -87.6500523};
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 4,
center: myLatLng
});
markers
}
</script>
<script async defer
src="https://maps.googleapis.com/maps/api/js?key=API_KEY&callback=initMap">
</script>
</body>
</html>
The answer outlined by #panavtec below also works and might have an easier api work work with!
A sample repository of his solution is here:
https://github.com/dazza5000/flutter_web_google_maps_example
If you need to use the library, I found this alternative:
Widget getMap() {
String htmlId = "7";
final mapOptions = new MapOptions()
..zoom = 8
..center = new LatLng(-34.397, 150.644);
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(htmlId, (int viewId) {
final elem = DivElement()
..id = htmlId
..style.width = "100%"
..style.height = "100%"
..style.border = 'none';
new GMap(elem, mapOptions);
return elem;
});
return HtmlElementView(viewType: htmlId);
}
I didn't tested it thoroughly but it seems to render the map.
A basic working example of this solution is here:
https://github.com/dazza5000/flutter_web_google_maps_example
Created a video walk-through of the project:
https://www.youtube.com/watch?v=iW7pCBL7yWk
Barebones blog article walking through solution:
http://whereisdarran.com/2020/01/google-maps-for-flutter-web/
Related
this is my first question here on stackoverflow.
I'm working on my first real app after attending a few CS classes at University and some courses on Udemy. So I'm lacking a lot of software engineering knowledge.
My goal: I want to build a search for stocks by using an external API endpoint. For that, I have created 4 dart files to handle the searchscreen with inputs (My UI), networking, parsing, and basically returning the data.
My NetworkAPI class to handle all sorts of network requests looks like this. I'm trying to use the jsonDecode already here and returning that. This class waits for an URL that will be put together in another class (financeData):
class NetworkAPI {
final String url;
NetworkAPI(this.url);
Future getData() async {
http.Response response = await http.get(url);
if (response.statusCode == 200) {
String data = response.body;
return jsonDecode(data);
} else {
print(response.statusCode);
}
}
}
This is basically the Json I want to parse. If I understood the theory correctly it's a map with a list of objects.
{
"bestMatches": [
{
"1. symbol": "TESO",
"2. name": "Tesco Corporation USA",
"3. type": "Equity",
"4. region": "United States",
"5. marketOpen": "09:30",
"6. marketClose": "16:00",
"7. timezone": "UTC-04",
"8. currency": "USD",
"9. matchScore": "0.8889"
}
{....}
]
}
To parse this, I saw some really good explanations here on stack overflow. I'm basically trying to retrieve the information I'm interested in.
class SearchOutput {
final List<BestMatch> bestMatches;
SearchOutput({this.bestMatches});
factory SearchOutput.fromJson(Map<String, dynamic> parsedJson){
var list = parsedJson['bestMatches'] as List;
print(list.runtimeType);
List<BestMatch> searchResultList = list.map((i) => BestMatch.fromJson(i)).toList();
return SearchOutput(
bestMatches: searchResultList,
);
}
}
class BestMatch {
String symbol;
String name;
String type;
String region;
String currency;
BestMatch({
this.symbol,
this.name,
this.type,
this.region,
this.currency,
});
factory BestMatch.fromJson(Map<String, dynamic> parsedJson){
return BestMatch(
symbol: parsedJson["1. symbol"],
name: parsedJson["2. name"],
type: parsedJson["3. type"],
region: parsedJson["4. region"],
currency: parsedJson["8. currency"],
);
}
}
Now I created a class with a method that should return all my data and format it into a list. The URL still needs an searchInput, which will be handed over from a TextField Widget in the UI.
const apiKey = 'demo';
const alphaVantageSearchUrl =
'https://www.alphavantage.co/query?function=SYMBOL_SEARCH';
class FinanceData {
Future<dynamic> getSearchData(String searchInput) async {
var url = '$alphaVantageSearchUrl&keywords=$searchInput&apikey=$apiKey';
NetworkAPI networkAPI = NetworkAPI(url);
var searchData = await networkAPI.getData();
SearchOutput searchOutput = new SearchOutput.fromJson(searchData);
return searchOutput;
}
}
Testing with some print statements seems to fullfill my goal of being able to access the data. For example printing out the name of the second object out of my response.
print(searchOutput.bestMatches[1].name);
The last file is my UI where we have a TextField, that I push to the financeData class in order to build the URL. Currently, my goal would be, to be able to show just the name of any given object in my response in the UI. I have no idea how to initialize my financeDate and how to use the return from my finaceData class in the UI. (I took some design styles out of the code snippet).
class SearchScreenWatchlist extends StatefulWidget {
#override
_SearchScreenWatchlistState createState() => _SearchScreenWatchlistState();
}
class _SearchScreenWatchlistState extends State<SearchScreenWatchlist> {
String searchInput;
FinanceData financeData = FinanceData();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Container(
child: TextField(
onChanged: (value) {
searchInput = value;
},
),
),
TextButton(
onPressed: () {
financeData.getSearchData(searchInput);
setState(() {
});
},
child: Text('Search')),
Text(('Search Results for: $searchInput')),
Container(child: Text('**SHOW HERE THE NAME**')),
],
),
);
}
}
It would be my goal to show in the last container the name on any given object from my response for example RESPONSE.name[1]. Later on I will try to iterate throw all the objects and show a list of all names of the response.
Thanks a lot! I really appreciate your help!
I think changing SearchScreenWatchlist to the following should display the name of the first bestMatches entry after you pressed the Search button (if I didn't miss something).
Make sure to look further into setState and initState (And the StatefulWidget lifecycle in general). Also it may not be the most elegant way to initialize FinanceData() and SearchOutput() this way - However, awesome starter project!
class SearchScreenWatchlist extends StatefulWidget {
#override
_SearchScreenWatchlistState createState() => _SearchScreenWatchlistState();
}
class _SearchScreenWatchlistState extends State<SearchScreenWatchlist> {
String searchInput;
FinanceData financeData;
SearchOutput searchOutput;
#override
initState() {
super.initState();
financeData = FinanceData();
searchOutput = SearchOutput();
}
_handleButtonPress() async {
final fetchedSearchOutput = await financeData.getSearchData(searchInput);
setState(() {
searchOutput = fetchedSearchOutput;
});
}
_buildSearchResult() {
if(searchOutput.bestMatches != null && searchOutput.bestMatches.isNotEmpty) {
return Text(searchOutput.bestMatches.first.name);
}
else {
return Text("No data fetched");
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Container(
child: TextField(
onChanged: (value) {
setState(() {
searchInput = value;
});
},
),
),
TextButton(
onPressed: () => _handleButtonPress(),
child: Text('Search'),
),
Text(('Search Results for: $searchInput')),
Container(
child: _buildSearchResult(),
),
],
),
);
}
}
I have a list of profile fields that I would like to create using a single widget, but I'm new to Flutter and can't seem to work out one single thing: passing a variable through a parameter. I have been able to create many widgets that work just fine, using this.variable = value, but now that I'm trying to convert it to a single widget as not to repeat myself, that's where I'm having the problem.
I have the following code (of course removing all that I believe to be unnecessary). Here it currently shows an error of The setter 'listType' isn't defined for the class '_ProfileDataState'.
class _ProfileDataState extends State<ProfileData> {
final _countries = DropDownLists.countries;
String _country;
var listType; //<-- added this per comments
Widget profileDropDown(var list, var listType) {
return Card(
onTap: () async {
AlertDialog(
content: DropdownButtonFormField<String>(
isExpanded: true,
items: list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
isDense: true,
value: listType,
onChanged: (value) {
FocusScope.of(context).requestFocus(FocusNode());
setState(() {
this.listType = value;
});
},
)
)
}
)
}
#override
Widget build(BuildContext context) {
return profileDropDown(_countries, _country),
...
class _ProfileDataState extends State<ProfileData> {
String _country;
var listType; // declare a variable
#override
void initState() {
super.initState();
listType = widget.listType; // assign it a value here
}
Widget profileDropDown(var listType) {
return Card(
onTap: () async {
AlertDialog(
onChanged: (value) {
FocusScope.of(context).requestFocus(FocusNode());
setState(() {
this.listType = value; // works
});
},
)
}
)
}
// other methods
}
Issue in brief:
trying to access location data of the user in background using location and workManager plugin.
Currently with the code mentioned below i am able to access the location information if the application is open, Since callbackDispatcher is a top level function i am not able to call the location plugin.
location plugin works when a call is done inside of the class. I am trying a way to access _getlocation() from callbackDispatcher, I am getting PlatformException(NO_ACTIVITY).
Things I have tried:
found few other guys facing similar issue here, here and here
Tired all these steps and no luck.
import 'package:flutter/material.dart';
import 'package:location/location.dart';
import 'package:workmanager/workmanager.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
Location location = new Location();
void callbackDispatcher() {
Workmanager.executeTask((task, inputData) {
if (task == "simplePeriodicTask") {
print("task working");
_getLocation();
}
return Future.value(true);
});
}
LocationData _location;
String _error;
double lat;
double long;
_getLocation() async {
_error = null;
try {
var _locationResult = await location.getLocation();
_location = _locationResult;
lat = _location.latitude;
long = _location.longitude;
} on PlatformException catch (err) {
_error = err.code;
}
if (_error == null) {
// _check();
print(lat);
} else {
//dialog
print(_error);
}
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
Workmanager.initialize(
callbackDispatcher, // The top level function, aka callbackDispatcher
isInDebugMode:
true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
);
_checkPermissions();
}
// Permission for location
PermissionStatus _permissionGranted;
// final Location location = new Location();
_checkPermissions() async {
PermissionStatus permissionGrantedResult = await location.hasPermission();
setState(() {
_permissionGranted = permissionGrantedResult;
});
if (_permissionGranted == PermissionStatus.DENIED) {
_requestPermission();
} else if (_permissionGranted == PermissionStatus.GRANTED) {
_checkService();
}
}
_requestPermission() async {
if (_permissionGranted != PermissionStatus.GRANTED) {
PermissionStatus permissionRequestedResult =
await location.requestPermission();
setState(() {
_permissionGranted = permissionRequestedResult;
});
if (permissionRequestedResult != PermissionStatus.GRANTED) {
return;
} else if (permissionRequestedResult == PermissionStatus.GRANTED) {
_checkService();
}
}
}
//Permission ends
//services enabled function
bool _serviceEnabled;
_checkService() async {
bool serviceEnabledResult = await location.serviceEnabled();
setState(() {
_serviceEnabled = serviceEnabledResult;
});
if (_serviceEnabled == false) {
_requestService();
} else {
// _getLocation();
}
}
_requestService() async {
if (_serviceEnabled == null || !_serviceEnabled) {
bool serviceRequestedResult = await location.requestService();
setState(() {
_serviceEnabled = serviceRequestedResult;
});
if (!serviceRequestedResult) {
return;
} else {
// _getLocation();
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dart'),
),
body: Column(children: <Widget>[
RaisedButton(
child: Text('get Location'),
onPressed: () {
Workmanager.registerPeriodicTask(
"2",
"simplePeriodicTask",
// When no frequency is provided the default 15 minutes is set.
// Minimum frequency is 15 min. Android will automatically change your frequency to 15 min if you have configured a lower frequency.
);
print('task registered');
_getLocation();
}),
RaisedButton(
onPressed: () async {
await Workmanager.cancelAll();
print('task Destroyd');
},
child: Text("cancel"),
),
]),
);
}
}
Trying to access _getlocation() from callbackDispatcher();
Any help on this is greatly appreciated.
I was facing same issue recently. location package not work with WorkManager plugin, I dont know the reason but here is my solution;
/// This Function calls only from WorkManager
/// Used GeoLocator instead of Location package due to PlatformException(NO_ACTIVITY) error throwing
Future<String> getPlaceMarkLocationWhileAppOff() async {
Geolocator geoLocator = Geolocator()..forceAndroidLocationManager = true;
var _position = await geoLocator.getCurrentPosition(
// desiredAccuracy: LocationAccuracy.high,
);
var value = await geoLocator.placemarkFromCoordinates(_position.latitude, _position.longitude);
return _placeMark = "${value.first.subLocality}\n${value.first.subAdministrativeArea}";
}
Used Geolocator package when app offline and used Location package when app online..
I hope it will help..
i have tried this to customize marker on flutter_google_maps and this to change widget into bytes, since we could change marker using bytes, not widget.
i actually solve the problem if i use only one type of marker like this:
but things are different where the requirement design just like this:
so how do i solve the problem?
here some code i use, but the result output is first image above, not as expected.
-> method to change widget into image
import 'dart:ui' as ui;
GlobalKey<ScaffoldState> keyScaffold = new GlobalKey<ScaffoldState>();
Future<Uint8List> _capturePng() async {
try {
RenderRepaintBoundary boundary =
keyScaffold.currentContext.findRenderObject();
ui.Image image = await boundary.toImage(pixelRatio: 3.0);
ByteData byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List();
return pngBytes;
} catch (e) {
print(e);
}
}
bool rendering = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Peta'),
),
body: rendering
? renderWidgetToImage()
: renderGoogleMap()
);
-> method to render widget before converted
String title;
Widget renderWidgetToImage() {
return RepaintBoundary(
key: keyScaffold,
child: Container(
margin: EdgeInsets.only(top: 30, left: 10, right: 10, bottom: 20),
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
color: Colors.green,
),
child: Text(
title,
style: TextStyle(
fontSize: 10
),
)),
);
}
-> method to programmatically add marker using widget
final Set<Marker> _markers = {};
#override
void initState() {
super.initState();
var arrMarker = <MarkerMap>[
MarkerMap("Text Widget 3","123",3.59196,98.672226),
MarkerMap("Text Widget 2","456",3.49196,97.572226),
MarkerMap("Text Widget 1","789",3.39196,97.772226),
];
for(int i =0; i< arrMarker.length; i++) {
setState(() {
this.title = arrMarker[i].title;
});
BitmapDescriptor.fromAssetImage(
ImageConfiguration(size: Size(48, 48)), DefaultImageLocation.iconAnalog)
.then((onValue) async {
var png = await _capturePng(keyScaffold);
setState(() {
this.myIcon = BitmapDescriptor.fromBytes(png);
this.rendering = false;
});
setState(() {
_markers.add(
Marker(
markerId: MarkerId(arrMarker[i].id),
position: LatLng(arrMarker[i].pos1, arrMarker[i].pos2),
icon: BitmapDescriptor.fromBytes(png),
),
);
});
});
setState(() {
this.rendering = true;
});
}
any help would be appreciated, thank you
Currently, this is now possible using the steps provided in this blog.
As mentioned in the intro:
We need to paint a Widget and then convert it to bitmap. But its
tricky because you cant simply do that. we have to place into widget
tree and fetch its painted bitmap.
For a rough summary, the steps mentioned were:
First
We need to get our bitmaps right after they are drawn. There
are multiple ways to do this, I use tiny AfterLayoutMixin available
here.
Second
Lets create our custom widget, yay!
My widget accepts name as an parameter. I used ClipPath for the
triangular Pointer arrow.
Third
Lets create a location object and list of locations and a
method to generate widgets from the location list.
i'm trying to do a Dashboard with multiples jsons requests, but i want that request work one per one, like when finish first request start the second, when finish second start the third when finish third start the N.
my list code:
new CustomScrollView(
cacheExtent: height * 6,
slivers: [
new SliverList(
delegate: new SliverChildListDelegate(
[
new RelatorioVendPeriodoAPeriodo(),
new RelatorioMensals(),
new RelatorioDiasDaSemanas(),
new RelatorioVendasTotalidasPorPeriodo(),
new RelatorioDasVendasTotsProdutos(),
]
)
)
]
),
this new classes calls, returns for me request. Anyone knows how to delay it?
First, the parent widget should return a progress bar when the necessary data is not available.
A service will be called an initState to fetch data from the backend. when data is ready setState() will be called to redraw the widget.
Look at this example:
class _TestWidgetState extends State<TestWidget> {
var data;
#override
void initState() {
data = NetworkService.getData().then((data) {
setState(() {
this.data = data;
});
});
}
#override
Widget build(BuildContext context) {
if (data == null) {
return CircularProgressIndicator();
} else {
return
new CustomScrollView(
cacheExtent: height * 6,
slivers: [
new SliverList(
delegate: new SliverChildListDelegate(
[
new RelatorioVendPeriodoAPeriodo(data: data),
new RelatorioMensals(data: data),
new RelatorioDiasDaSemanas(data: data),
new RelatorioVendasTotalidasPorPeriodo(data: data),
new RelatorioDasVendasTotsProdutos(data: data),
]
)
)
]
);
}
}
}
class NetworkService {
final JsonDecoder _decoder = new JsonDecoder();
static String data1;
static String data2;
static getData() async {
if (data1 == null || data2 == null) {
await fetchFromServer();
}
return {'data1': data1, 'data2': data2};
}
static fetchFromServer() async {
data1 = (await http.get('url')).body;
data2 = (await http.get('url')).body;
}
}
Future.delayed(const Duration(milliseconds: 500), () {
print(" This line is executed after 5 seconds");
});