Google Maps Flutter Custom marker not showing - ios

Because I had problems with Google Maps and Bitmap descriptor, I tried to preload the icons before loading the Map, like this:
Widget preloadPinsAndLoadSearchMap() {
final config = ImageConfiguration(size: Size(48, 48));
final activePin = 'assets/active-pin.png';
final neutralPin = 'assets/neutral-pin.png';
final home = 'assets/home-map.png';
return FutureBuilder(
future: Future.wait([
BitmapDescriptor.fromAssetImage(config, activePin),
BitmapDescriptor.fromAssetImage(config, neutralPin),
BitmapDescriptor.fromAssetImage(config, home),
]),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return SearchMap(pins: snapshot.data);
} else {
return Container();
}
},
);
}
And the SearchMap and the Markers:
// Each Marker is produced like this:
Marker createMarker(dynamic seller) {
return Marker(
markerId: MarkerId(seller.id),
position: LatLng(seller.latitude, seller.longitude),
onTap: () => onMarkerTap(seller),
// The icons are coming from the FutureBuilder.
icon: isActive ? activePin : neutralPin,
);
}
class SearchMap extends StatelessWidget {
Widget build(BuildContext context) {
final paris = initialPosition ??
LatLng(parisPosition.latitude, parisPosition.longitude);
return GoogleMap(
myLocationButtonEnabled: false,
zoomControlsEnabled: false,
compassEnabled: false,
mapType: MapType.normal,
markers: markers,
initialCameraPosition: CameraPosition(target: paris, zoom: 15),
onMapCreated: onMapCreated,
onTap: (obj) => print(obj),
);
}
}
Happily it works on my devices very well, both iOS and Android. But on my reviewers devices, the icons are just not showing up. Is it a correct way to handle BitmapDescriptor? Maybe I’m wrong on the way to do this?

Related

How to generate Timer in flutter with different duration?

I tried this,
var e = [2, 5, 6, 7];
e.forEach((element) {
print(element);
Timer.periodic(new Duration(seconds: element), (timer) {
debugPrint(element.toString());
sleep(Duration(seconds: element));
});
});
But it seems messed up and the timer did not work as expected.
the output is 2,5,2,6,7,5,2,6,7
the expected output is 2,5,6,7,2,5,6,7,2,5,6,7
My target is I need to generate like GIF images with different timer.
I need to extract the html code in dart, inside the html code have image path and time for each image should be displayed.
Currently, I can generate image through image network. But still in problem with the timer. I tried like code above, but the output is wrong.
Here is full code,
import 'dart:async';
import 'dart:developer';
import 'dart:ffi';
import 'dart:io';
import 'dart:convert';
import 'package:audioplayers/audio_cache.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:flutter/src/foundation/constants.dart';
import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart' as Dom;
import 'package:shared_preferences/shared_preferences.dart';
// import 'package:firebase_admob/firebase_admob.dart';
import 'player_widget.dart';
typedef void OnError(Exception exception);
const kUrl1 =
'http://n0c.radiojar.com/1t04vq7uc6quv?rj-ttl=5&rj-tok=AAABcd71RMcAnUEp0f_GRjz3pw';
const String testDevice = 'MobileId';
void main() {
runApp(MaterialApp(debugShowCheckedModeBanner: false, home: ExampleApp()));
}
class ExampleApp extends StatefulWidget {
#override
_ExampleAppState createState() => _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
AudioCache audioCache = AudioCache();
AudioPlayer advancedPlayer = AudioPlayer();
String localFilePath;
String a;
#override
void initState() {
super.initState();
if (kIsWeb) {
// Calls to Platform.isIOS fails on web
return;
}
if (Platform.isIOS) {
if (audioCache.fixedPlayer != null) {
audioCache.fixedPlayer.startHeadlessService();
}
advancedPlayer.startHeadlessService();
}
}
Future saveCompanyAds() async {
final response = await Client().get("http://www.putrajaya.fm/");
final SharedPreferences prefs = await SharedPreferences.getInstance();
final List<String> listLogo = [];
final List<String> listBanner = [];
final List<int> listNumberLogo = [];
final List<int> listNumberBanner = [];
final List<int> listTimeLogo = [];
final List<int> listTimeBanner = [];
final List<Tag> tagsBanner = [];
final List<Tag> tagsLogo = [];
RegExp expBanner = new RegExp(r'(gambar)\[[0-9]\].+');
RegExp expLogo = new RegExp(r'(images)\[[0-9]\].+');
RegExp expNumArr = new RegExp(r'\[[0-9]+\]');
RegExp expURL =
new RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+');
RegExp expTime = new RegExp(r'(\/\/time=)[0-9]+');
prefs.remove("banner");
prefs.remove("logo");
if (response.statusCode == 200) {
var document = parse(response.body);
String text = document.getElementById("radio_jar").outerHtml;
Iterable<RegExpMatch> matchesBanner = expBanner.allMatches(text);
Iterable<RegExpMatch> matchesLogo = expLogo.allMatches(text);
matchesBanner.forEach((matchB) {
listBanner.add(text.substring(matchB.start, matchB.end));
});
matchesLogo.forEach((matchL) {
listLogo.add(text.substring(matchL.start, matchL.end));
});
listBanner.forEach((fB) {
var a = expNumArr.stringMatch(fB);
var b = a.replaceAll(RegExp('[^0-9]+'), '');
var c = int.parse(b);
listNumberBanner.add(c);
var g = expURL.stringMatch(fB);
var d = expTime.stringMatch(fB);
var e = d.replaceAll(RegExp('[^0-9]+'), '');
var f = int.parse(e);
listTimeBanner.add(f);
tagsBanner.add(Tag(c, g, f));
});
listLogo.forEach((fL) {
var a = expNumArr.stringMatch(fL);
var b = a.replaceAll(RegExp('[^0-9]+'), '');
var c = int.parse(b);
listNumberLogo.add(c);
var g = expURL.stringMatch(fL);
var d = expTime.stringMatch(fL);
var e = d.replaceAll(RegExp('[^0-9]+'), '');
var f = int.parse(e);
listTimeLogo.add(f);
tagsLogo.add(Tag(c, g, f));
});
String jsonTagsBanner = jsonEncode(tagsBanner);
String jsonTagsLogo = jsonEncode(tagsLogo);
prefs.setString("banner", jsonTagsBanner);
prefs.setString("logo", jsonTagsLogo);
}
}
Future readCompanyAds() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
List listBannerJSONDecode = [];
List listLogoJSONDecode = [];
listBannerJSONDecode = jsonDecode(prefs.getString("banner"));
listLogoJSONDecode = jsonDecode(prefs.getString("logo"));
List<String> bannerList = List<String>();
List<int> timerbannerList = List<int>();
final List<String> listBannerNetwork = List<String>();
final List<int> listBannerTimer = List<int>();
listBannerJSONDecode.forEach((elementBanner) => {
listBannerNetwork.add(elementBanner['url']),
listBannerTimer.add(elementBanner['time'])
});
listBannerNetwork.forEach((urlBanner) {
bannerList.add(urlBanner);
});
//Currently I insert the first element to avoid app crash
a = bannerList[0];
listBannerTimer.forEach((timeBanner) {
timerbannerList.add(timeBanner);
});
//this is where I want to insert the GIF with different timer by using
//timerbannerList and I want to replace it with the variable "a" above.
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([saveCompanyAds(), readCompanyAds()]),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: [
new CircularProgressIndicator(
backgroundColor: Colors.white,
),
],
),
);
} else {
if (snapshot.hasError)
return Center(child: Text('Error: ${snapshot.error}'));
else
return MultiProvider(
providers: [
StreamProvider<Duration>.value(
initialData: Duration(),
value: advancedPlayer.onAudioPositionChanged),
],
child: Scaffold(
appBar: AppBar(
title: Text('Putrajaya FM'),
),
body: Column(
children: <Widget>[
Image.asset('images/putrajayafm.jpg',
fit: BoxFit.fill,
height: 200,
width: double.infinity),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[PlayerWidget(url: kUrl1)])),
Container(
margin: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
//this is where I call the "a" variable,
//where it should be repalced with list of
//image with different timer
Image.network(a,
fit: BoxFit.fill,
height:
(MediaQuery.of(context).size.width / 2),
width:
(MediaQuery.of(context).size.width / 2)),
Image.asset('images/logo2.gif',
fit: BoxFit.fill,
height:
(MediaQuery.of(context).size.width / 2),
width:
(MediaQuery.of(context).size.width / 2))
],
)),
Expanded(
child: Container(
child: Image.network(a,
fit: BoxFit.fill,
width: MediaQuery.of(context).size.width),
))
],
)),
);
}
},
);
}
}
class Tag {
int number;
String url;
int time;
Tag(this.number, this.url, this.time);
Map<String, dynamic> toJson() => {'number': number, 'url': url, 'time': time};
}
The expected behavior of the code you have shown is exactly what you see.
It sounds like what you really want is a, say, 10 second repeating interval, and then events at 2, 5, 6 and 7 seconds into each interval.
You can do that in a number of different ways. The one with the fewest timers use four repeating timers, and four non-repeating timers to start them at different times:
void startTimers(
Duration repeat, List<Duration> offsets, void Function(Duration) callback) {
for (var e in offsets) {
Timer(e, () {
callback(e); // First call.
Timer.peridic(repeat, (_) => callback(e)); // More calls after that.
});
}
}
Another option is to have a single repeating timer, and then start non-repeating timers for each offset:
Timer startTimers(
Duration repeat, List<Duration> offsets, void Function(Duration) callback) {
void go(_) {
for (var e in offsets) Timer(e, () => callback(e));
}
go(null);
return Timer.periodic(repeat, go);
}
If you need to stop the timers again, then I'd go with the latter approach, and accept that the current round will run to completion even if I try to cancel. (Otherwise you'll need some more complex code to be able to retain all the timers and cancel them).

How to make sure that async function is loaded before Widget build?

I have a screen with some informations about the user.
In my build widget I have this:
Column
(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>
[
Text('User Points', style: TextStyle(color: Colors.blueAccent)),
Text('${this.user.points}', style: TextStyle(color: Colors.black, fontWeight: FontWeight.w700, fontSize: 34.0))
],
),
And I have a function to update the dashboard each time user access it.
void updateDashboard() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
var api_key = prefs.getString('api_key');
var userID = prefs.getInt('userID');
var res = await http.post(('http://myApp.com/getDashboardPreferences/'+userID.toString() + '/' + api_key),
headers: {
"Accept": "application/json",
"content-type": "application/json"
});
this.user = User.fromJson(json.decode(res.body));
setState(() {
if (res.statusCode == 200) {
this.user.setPoints(this.user.points);
} else {
print("Something is wrong");
}
});
}
And in initstate I call that function:
#override
void initState() {
updateDashboard();
super.initState();
}
This works, but however, I get that ${this.user.points} is null for a few seconds (I get that warning screen) and then it is loaded and everything is fine. I guess it is because my function is async... But how can I assure that this updateDashboard() is called before rendering Widget build() ?
If you don't want to use FutureBuilder then you can do something like
if(done)
Column( /* whatever */ )
else
Center(child: CircularProgressIndicator())
void updateDashboard() async{
/* whatever */
if (res.statusCode == 200) {
setState(() {
this.user.setPoints(this.user.points);
done = true;
});
}
#override
void initState() {
/* whatever */
done = false;
}
Well, async means that the data might take time to load and is handed off to another thread to load. There can be multiple non blocking ways to do this:
Provide a default value for the user object, so by the time the async method finishes, you show proxy/default values and setState() makes sure the new value gets displayed when loaded.
This method requires you to load widgets conditionally, i.e load Text widget when there's data else load a Container().
Example for the second approach:
this.user!=null?Text('${this.user.points}'):Container()
This way you don't need to show the widget till the value loads. Either of the approaches work and depending on your use-case, you can choose which you can apply.
You can put the function right before the return widget.
You can make a loading variable like bool _loading then anywhere you start loading you async function you can setState of loading to true then setState of loading to false when async function stops loading like this.
void updateDashboard() async{
setState(() {
_loading = true;
});
SharedPreferences prefs = await SharedPreferences.getInstance();
var api_key = prefs.getString('api_key');
var userID = prefs.getInt('userID');
var res = await http.post(('http://myApp.com/getDashboardPreferences/'+userID.toString() + '/' + api_key),
headers: {
"Accept": "application/json",
"content-type": "application/json"
});
this.user = User.fromJson(json.decode(res.body));
setState(() {
if (res.statusCode == 200) {
this.user.setPoints(this.user.points);
} else {
print("Something is wrong");
}
_loading = false; //Set Loading to false
});
}
Then in your widget build, check for when loading is true and show a progress indicator like this.
body: _loading
? Center(
child: CircularProgressIndicator( //Show a Circular Progress indicator
valueColor: new AlwaysStoppedAnimation<Color>(Colors.cyan),
),
)
:Column(//Show your Dashboard)
I hope it helps

Future builder(future:) is recursing my http requests,

I need to fetch data from three different url before page rendering. So,
this is the method inside my ScopedModel includes multiple http.post methods:
Future fetchData() async {
_isLoading = true;
notifyListeners();
await fetchAvailable();
await fetchOnProgress();
await fetchCompleted();
_isLoading = false;
notifyListeners();
}
The methods inside of fetchData area just classic http.post request with raw Future type.
This is my FutureBuilder:
FutureBuilder(
future: model.fetchData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.connectionState != ConnectionState.done
? Center(
child: CircularProgressIndicator(),
)
: Column.......
The problem is, the 'future' function, it's executes constantly and never ends. My algorithm about, fetching the json from server, inflates the variables within body into ListView.builder descendants.
The output is as I said recursive post requests. Also, I'm getting this logs, the number of lines increments like 1 - 2 - 3 or 2 - 4 - 6 -8 etc.
uid=10085(my.package.directory) 1.ui identical 1 lines
.......Other logs here
uid=10085(my.package.directory) 1.ui identical 3 lines
And goes on like that...
Also, is there any other useful way to handle that small amount of data before page rendering?
Do not make HTTP requests in FutureBuilder
https://docs.flutter.io/flutter/widgets/FutureBuilder-class.html
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateConfig, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.
Here is a possible right solution. This makes your data load just one time inside scaffold. First make sure you are using stateful widget.
Define your data:
Future<List<Data>> futureList;
Make a function to fetch your data from backend:
Future<void> getList() async {
final provider = Provider.of<DataProvider>(context, listen: false);
final list = provider.getData();
setState((){
futureList = list;
});
}
Load your function in init state:
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
getList();
});
}
Call your fetched data in future builder:
Widget _body(BuildContext context) {
return FutureBuilder(
future: futureList,
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if (snapshot.hasData) {
if(snapshot.data.length == 0){
return Center(child: Text('List is empty'));
}else {
return ListView.builder(
padding: EdgeInsets.only(left: 5, right: 5, top: 5, bottom: 5),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
child: DataItem(
index: index,
dataClass: snapshot.data[index],
),
onTap: () {});
},
itemCount: snapshot.data.length);
}
} else {
return Center(child: CircularProgressIndicator());
}
},
);

Flutter show Progress HUD while doing background task

I am making movie maker app where i am showing progress hud when creating movie from list of videos.But Progress HUD progress indicator UI stuck at starting point. Following is my code:
class _MyHomePageState extends State<MyHomePage> {
static List<dynamic> videosPath = List();
static const MethodChannel methodChannel =
const MethodChannel('moviemaker.devunion.com/movie_maker_channel');
String _batteryLevel = 'Battery level: unknown.';
ProgressHUD _progressHUD;
bool _loading = false;
#override
void initState() {
super.initState();
// _getBatteryLevel();
_progressHUD = new ProgressHUD(
backgroundColor: Colors.black12,
color: Colors.white,
containerColor: Colors.blue,
borderRadius: 5.0,
text: 'Loading...',
loading: false,
);
}
#override
Widget build(BuildContext context) {
Widget child = new Scaffold(
appBar: new AppBar(
title: new Text('Movie Maker'),
actions: <Widget>[
// action button
IconButton(
icon: Icon(Icons.movie_creation),
onPressed: () {
_createMovieSync(videosPath);
},
)
],
),
body: Stack(children: [_progressHUD, _buildContentSection()]),
floatingActionButton: new FloatingActionButton(
onPressed: () {
debugPrint("Bipin - FAB pressed");
pickVideos().asStream().listen(_setResults);
},
tooltip: "Pick a Video",
child: new Icon(Icons.add),
),
);
return child;
}
void toggleProgressHUD() {
setState(() {
if (_loading) {
_progressHUD.state.dismiss();
} else {
_progressHUD.state.show();
}
_loading = !_loading;
});
}
void _createMovieSync(List<dynamic> paths) {
toggleProgressHUD();
_createMovie(videosPath).then((moviePath) {
debugPrint("Bipin - _createMovieSync Movie created path: $moviePath");
_startMovie(moviePath).then((_) {
debugPrint("Bipin - start movie then");
}).catchError((error) {
debugPrint("Bipin - start movie error:");
}).whenComplete(() {
debugPrint("Bipin - start movie completed.");
toggleProgressHUD();
});
}).catchError((error) {
debugPrint("Bipin - Create movie error: ${error.toString()}");
}).whenComplete(() {
debugPrint("Bipin - Create movie completed.");
});
}
Future<String> _createMovie(List<dynamic> paths) {
return Future<String>(() {
debugPrint("Bipin - Create movie future going to sleep");
sleep(Duration(seconds: 10));
debugPrint("Bipin - Create movie future wake up");
return "FilePath goes here";
});
// return methodChannel.invokeMethod('createMovie', {"videoPaths": paths});
}
Future<Null> _startMovie(String moviePath) async {
return Future<Null>(() {
debugPrint("Bipin - Start movie future going to sleep");
sleep(Duration(seconds: 10));
debugPrint("Bipin - Start movie future wake up");
});
// debugPrint("Bipin - Created Movie path: $moviePath");
// return methodChannel.invokeMethod('startMovie', {"moviePath": moviePath});
}
Here, I am starting movie creation when a user taps on Create Movie action button and showing progress HUD but it is not working as expected, It's showing just like here.
Updated code to use modal_progress_hud 0.0.6, As suggested in answer But it's still, showing hanging progress hud. Find code here: https://github.com/bipinvaylu/MovieMaker-Flutter/commit/43b317efc9fdbc6e67cf36b16e4350587bf517ae
I take a slightly different approach with the modal_progress_hud package which wraps the widget you wish to make modal (with a progress indicator). After the async call returns you turn off the progress indicator. You can then play the movie.
No matter which package you use, I would probably switch to another widget to play the movie triggered by a call to setState() after making a call to stop the progress indicator. Playing the movie before the original async call completes may be preventing the progress indicator from stopping. Food for thought.

Programmatically focusing on InputField and opening up keyboard

I have a situation where I need to programmatically focus on a InputField(such as in response to a button press).
I'm using the Focus.moveTo function; however, even though the InputField is focused (blinking cursor appears) the keyboard is not brought up.
It seems like the best solution would be to call the RequestKeyboard() function in _InputFieldState, but this is private.
What would be the best way to accomplish this?
Here is a code sample showing the workflow:
class InputFieldWrapper extends StatefulWidget {
#override
_InputFieldWrapperState createState() => new _InputFieldWrapperState();
}
class _InputFieldWrapperState extends State<InputFieldWrapper> {
InputValue _currentInput = new InputValue(text: 'hello');
// GlobalKey for the InputField so we can focus on it
GlobalKey<EditableTextState> _inputKey = new GlobalKey<EditableTextState>();
#override
Widget build(BuildContext context) {
return new Column(
children: [
// Button that should focus on the InputField when pressed
new IconButton(
icon: new Icon(Icons.message),
onPressed: () {
Focus.moveTo(_inputKey);
},
),
// InputField that should be focused when pressing the Button
new InputField(
value: _currentInput,
key: _inputKey,
onChanged: (InputValue input) {
setState(() {
_currentInput = input;
});
}
),
],
);
}
}
This was decided to be a bug and is being tracked at #7985.

Resources