Im a newbie in flutter. I'm making a project and I want to know how can I render the list of points using latlong plugin from flutter inside flutter map. I was able to render the first point but unable to show the other points in the list. Can you please guide me or correct my code.
Widget _mapBuilder(BuildContext context) {
final List<LatLng> _mapPoints = [
LatLng(9.7, 123.1),
LatLng(10.7, 124.9),
LatLng(11.7, 125.05)
];
return FlutterMap(
options: MapOptions(
center: _mapPoints[1],
zoom: 16.0,
),
layers: [
TileLayerOptions(
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c'],
attributionBuilder: (_) {
return const Text("© OpenStreetMap contributors");
},
),
MarkerLayerOptions(markers: [
for (var point in _mapPoints) ...[
Marker(
width: 15.0,
height: 15.0,
point: point,
builder: (ctx) => Container(
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(50.0),
border: Border.all(color: Colors.white, width: 2)),
),
),
]
]),
],
);
You can create markers list like this and use that list in Markers parameters required in marker layer.
final List<LatLng> _mapPoints = [
LatLng(12.9, 77.8),
LatLng(12.1, 77.2),
LatLng(12.5,77.4),
];
for (LatLng i in _mapPoints){
lister.add(Marker(point: i, builder:(ctx) =>
Container(child: Column(
children: [
Icon(Icons.add,size: 40,),
Text("data",style: TextStyle(fontSize: 12),)
],
),
)));
}
Related
I have a Flutter app which requires location services. I have already implemented getting a users current location, permissions, etc...
My app has to position a marker on a map when first open based on the current location of the user. (This is what I need help with)
The user can later touch the map at any other point to place the marker at that point. (This I have already done)
I have successfully created a function which can retrieve the current location of a user like this, (Do note that that function is async)
Future<LatLng> get currentLocation async {
Position pos = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
return LatLng(pos.latitude, pos.longitude);
}
I currently have LatLng markerPoint = LatLng(12.9716, 77.5946); as my initial marker point inside of my HomePage like this,
class _HomePageState extends State<HomePage> {
// Initial marker location
LatLng markerPoint = LatLng(12.9716, 77.5946);
...
But I want the current location returned by my function to replace the values given here. I am unable to do something like LatLng markerPoint = await _locationService.currentLocation; because I cannot make that part of the class async.
So, that's basically my problem. I want to initialise my variable through an asynchronous function, but I haven't found a way to do that since the area I am in, does not allow for async functions.
Help will be appreciated.
PS:
My remaining build function which sets the marker point whenever touched is over here for your reference.
#override
Widget build(BuildContext context) {
return Stack(
children: [
FlutterMap(
options: MapOptions(
onTap: (tapPosition, point) async {
setState(() {
markerPoint = point;
});
},
//
center: markerPoint,
zoom: 10.0,
// this is required to disable rotation of the map
// the map will behave wierd when you rotate it if it's deleted
// the map will not scroll properly and not place markers at the exact locations.
// if the below line in removed
interactiveFlags: InteractiveFlag.all & ~InteractiveFlag.rotate,
),
nonRotatedChildren: [
TileLayer(
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: const ['a', 'b', 'c'],
),
MarkerLayer(
markers: [
Marker(
width: 100.0,
height: 100.0,
point: markerPoint,
builder: (ctx) => const Icon(
Icons.location_on,
color: Colors.red,
size: 40,
),
),
],
),
],
),
SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Card(
child: TextField(
decoration: const InputDecoration(
prefixIcon: Icon(Icons.location_on_outlined),
hintText: "Search for a location",
contentPadding: EdgeInsets.all(16.0),
),
onTap: () async {
// This is here for debugging only, kindly ignore
await requestPermission();
},
),
),
],
),
),
),
],
);
}
hope you are calling the get current location api in the initState() method
eg :
#override
void initState() {
super.initState();
fetchCurrentLocation();
}
void fetchCurrentLocation() async{
//calling the api
var currentLocation = await _locationService.currentLocation;
//setState will update the values in real time
setState(() {
markerPoint = currentLocation
});
}
I have a map in flutter where I have to commute the user to a specific point using directions. The red marker on the map when on pressed automatically shows directions on the bottom right but I want to show an arrow head to the user to click the directions on the bottom right because user may not notice it by themselves: https://imgur.com/a/lv9Xq9T
This is the code snippet :
void getLocation() async {
var location = await currentLocation.getLocation();
currentLocation.onLocationChanged.listen((LocationData loc) {
mapcontroller.animateCamera(CameraUpdate.newCameraPosition(CameraPosition(
target: LatLng(loc.latitude ?? 0.0, loc.longitude ?? 0.0), zoom: 5)));
setState(() {
clat = loc.latitude;
clng = loc.longitude;
_markers.addAll({
Marker(
markerId: const MarkerId('Shopkeeper Position'),
icon: BitmapDescriptor.defaultMarker,
// position: LatLng(20.7708612, 73.7235274))
position: LatLng(
widget.latitude,
widget.longitude,
),
onTap: () {
print("");
print("");
print("Marker pressed");
print("");
print("");
Container(
alignment: Alignment.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Get Directions",
style: GoogleFonts.roboto(
fontSize: 40,
fontWeight: FontWeight.w800,
color: Colors.blue,
),
),
Image.asset(
'assets/arrowHead.gif',
width: 200,
height: 200,
),
],
),
);
},
)
});
});
getPolypoints();
});
}
All i get when I tap on the marker are the print statements. I would want to display the row widget as well. How can i do this ?
You need to move your directions container and show/hide depending on the onTap.
Something like this:
isTapped ?? directionsContainer : Container() // empty container if isTapped is false
Update your onTap: to update state with a boolean:
setState(() {
isTapped = !isTapped;
})
I have a SliverGrid. I have a search field. In my search field onChange event I have a function that searches my local sqlite db based on the keyword entered by the user returns the results and reassigns to a variable and calls notifyListeners(). Now my problem is for some weird reason whenever I search for an item the wrong item is rendered.
I checked the results from my functions by iterating over the list and logging the title and the overall count as well and the results were correct however my view always rendered the wrong items. Not sure how this is possible.
I also noticed something strange, whenever it rendered the wrong item and I went back to my code and hit save, triggering live reload, when I switched back to my emulator it now displayed the right item.
I have tried the release build on an actual phone and it's the same behaviour. Another weird thing is sometimes certain items will duplicate and show twice in my list while the user is typing.
This is my function that searches my sqlite db:
Future<List<Book>> searchBookshelf(String keyword) async {
try {
Database db = await _storageService.database;
final List<Map<String, dynamic>> rows = await db
.rawQuery("SELECT * FROM bookshelf WHERE title LIKE '%$keyword%'; ");
return rows.map((i) => Book.fromJson(i)).toList();
} catch (e) {
print(e);
return null;
}
}
This is my function that calls the above function from my viewmodel:
Future<void> getBooksByKeyword(String keyword) async {
books = await _bookService.searchBookshelf(keyword);
notifyListeners();
}
This is my actual view where i have the SliverGrid:
class BooksView extends ViewModelBuilderWidget<BooksViewModel> {
#override
bool get reactive => true;
#override
bool get createNewModelOnInsert => true;
#override
bool get disposeViewModel => true;
#override
void onViewModelReady(BooksViewModel vm) {
vm.initialise();
super.onViewModelReady(vm);
}
#override
Widget builder(BuildContext context, vm, Widget child) {
var size = MediaQuery.of(context).size;
final double itemHeight = (size.height) / 4.3;
final double itemWidth = size.width / 3;
var heading = Container(
margin: EdgeInsets.only(top: 35),
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Books',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w900),
),
Text(
'Lorem ipsum dolor sit amet.',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 14),
),
],
),
),
);
var searchField = Container(
margin: EdgeInsets.only(top: 5, left: 15, bottom: 15, right: 15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(15)),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 1.0,
spreadRadius: 0.0,
offset: Offset(2.0, 1.0), // shadow direction: bottom right
),
],
),
child: TextFormField(
decoration: InputDecoration(
border: InputBorder.none,
prefixIcon: Icon(
FlutterIcons.search_faw,
size: 18,
),
suffixIcon: Icon(
FlutterIcons.filter_fou,
size: 18,
),
hintText: 'Search...',
),
onChanged: (keyword) async {
await vm.getBooksByKeyword(keyword);
},
onFieldSubmitted: (keyword) async {},
),
);
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.only(left: 1, right: 1),
child: LiquidPullToRefresh(
color: Colors.amber,
key: vm.refreshIndicatorKey, // key if you want to add
onRefresh: vm.refresh,
showChildOpacityTransition: true,
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Column(
children: [
heading,
searchField,
],
),
),
SliverToBoxAdapter(
child: SpaceY(15),
),
SliverToBoxAdapter(
child: vm.books.length == 0
? Column(
children: [
Image.asset(
Images.manReading,
width: 250,
height: 250,
fit: BoxFit.contain,
),
Text('No books in your bookshelf,'),
Text('Grab a book from our bookstore.')
],
)
: SizedBox(),
),
SliverPadding(
padding: EdgeInsets.only(bottom: 35),
sliver: SliverGrid.count(
childAspectRatio: (itemWidth / itemHeight),
mainAxisSpacing: 20.0,
crossAxisCount: 3,
children: vm.books
.map((book) => BookTile(book: book))
.toList(),
),
)
],
),
))));
}
#override
BooksViewModel viewModelBuilder(BuildContext context) =>
BooksViewModel();
}
Now the reason I am even using SliverGrid in the first place is because I have a search field and a title above the grid and I want all items to scroll along with the page, I didn't want just the list to be scrollable.
I believe this odd behavior can be attributed to you calling vm.getBooksByKeyword() in onChanged. As this is an async method, there is no guarantee that the last result returned will be the result for the final text in the TextFormField. The reason you see the correct results after a live reload is because the method is being called again with the full text currently in the TextFormField.
The quickest way to verify this is to move the function call to onFieldSubmitted or onEditingComplete and see if it behaves correctly.
If you require calling the function with every change to the text, you will need to add a listener to the controller and be sure to only make the call after input has stopped for a specified amount of time, using a Timer, like so:
final _controller = TextEditingController();
Timer _timer;
...
_controller.addListener(() {
_timer?.cancel();
if(_controller.text.isNotEmpty) {
// only call the search method if keyword text does not change for 300 ms
_timer = Timer(Duration(milliseconds: 300),
() => vm.getBooksByKeyword(_controller.text));
}
});
...
#override
void dispose() {
// DON'T FORGET TO DISPOSE OF THE TextEditingController
_controller.dispose();
super.dispose();
}
...
TextFormField(
controller: controller,
...
);
So I found the problem and the solution:
The widget tree is remembering the list items place and providing the
same viewmodel as it had originally. Not only that it also takes every
item that goes into index 0 and provides it with the same data that
was enclosed on the Construction of the object.
Taken from here.
So basically the solution was to add and set a key property for each list item generated:
SliverPadding(
padding: EdgeInsets.only(bottom: 35),
sliver: SliverGrid(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: (itemWidth / itemHeight),
mainAxisSpacing: 20.0,
),
delegate: SliverChildListDelegate(vm.books
.map((book) => BookTile(
key: Key(book.id.toString()), book: book))
.toList()),
),
)
And also here:
const BookTile({Key key, this.book}) : super(key: key, reactive: false);
My search works perfectly now. :)
I was trying to pass a list inside CupertinoPicker using loops but I couldn't figure it
this image contains the function I was trying to build
const List<String> currenciesList = [
'AUD',
'BRL',
'CAD',
'CNY',
'EUR',
'GBP',
'HKD',
'IDR',
'ILS',
'INR',
'JPY',
'MXN',
'NOK',
];
Container(
height: 150.0,
alignment: Alignment.center,
padding: EdgeInsets.only(bottom: 30.0),
color: Colors.lightBlue,
child:CupertinoPicker(
backgroundColor: Colors.lightBlue,
itemExtent: 32.0,
onSelectedItemChanged: (selectedIndex){
print(selectedIndex);
}, children:[
Text('USD',style: whiteColor ),
Text('EUR' , style: whiteColor),
Text('GDP', style:whiteColor),
]
),
),
As of Dart 2.3 you can use Collection For:
CupertinoPicker(
children:[
for (String name in currenciesList) Text( name ,style: whiteColor ),
]
)
You should create a Func to get all value in your list.
List<Widget> getPickerItems() {
List<Text> itemsCurrency = [];
for (var currency in currenciesList) {
itemsCurrency.add(Text(currency));
}
return itemsCurrency;
}
and add it in to children of CupertinoPicker:
CupertinoPicker(
children: getPickerItems(),
)
I'm just starting with Flutter and very new to State/Stream/BloC concepts. I'm trying to update a FlutterMap center based on the coordinates from a Geolocator()'s Position stream. At the moment I'm just calling setState() updating a variable with the new value, but next I'll be trying using the Bloc pattern instead..
Now, as well as I set the center on the coordinates of the Position coming from the stream I also set a Marker on the same coordinates. I was expecting to see a Steady marker in the center of the screen and a moving map underneath but is the opposite.. the Map is steady and the Icon is moving.. Can you see where I got it wrong?
Also, for the stream: property of the StreamBuilder how would I get to pass the stream from Geolocator() as an event?
Many thanks for your time and help.
This is the Stream:
StreamSubscription positionStream = _geolocator
.getPositionStream(locationOptions)
.listen((Position position) {
setState(() {
_position = position;
});
print(
_position.latitude.toString() + ',' + _position.longitude.toString()); // ok
});
And this is the Map and the Marker:
Container(
height: 670,
width: 370,
child: FlutterMap(
options: MapOptions(
center: LatLng(_position.latitude, _position.longitude),
minZoom: 16.0,
maxZoom: 19.0,
),
layers: [
TileLayerOptions(
// urlTemplate:
// 'https://api.openrouteservice.org/mapsurfer/{z}/{x}/{y}.png?api_key=omitted',
urlTemplate:
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c'],
keepBuffer: 20),
new MarkerLayerOptions(
markers: [
new Marker(
point: new LatLng(
_position.latitude, _position.longitude),
height: 200,
width: 200,
builder: (context) => IconButton(
icon: Icon(Icons.location_on),
color: Colors.red,
iconSize: 60,
onPressed: () {
print('icon tapped');
},
)),
],
),
],
),
),
Found the problem. I didn't assign any MapController().
So I created one and assign it to the FlutterMap. Inside the setState() I added _controller.move(LatLng(_position.latitude, _position.longitude), 16.0); and now the behaviour is the expected one. Steady Icon in the center of the screen and a moving map underneath.
Hope this will help others.
But still I will try to achieve the same with a BLoC pattern.
Cheers