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).
Related
I add a event to bloc immediately after pop a CupertinoModalPopup. But yield a state and not received in bloc listener. The code is like below.There are more states and events, and it's all working, except this state is not received. I don't know why, maybe it's because I add the event after the showCupertinoModalPopup was pop. I am not sure. I didn't paste the whole codes here, I only kept the relevant code. I did set the breakpoint on yield ProfileUploadAvatarSucceed(user), and it stops here.
BlocBuilder<ProfileBloc, ProfileState>(
builder: (context, state) {
return GestureDetector(
onTap: () async {
final file = await showCupertinoModalPopup(
context: context,
builder: (popupContent) {
return CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
child: Text('Photo Library'),
onPressed: () async {
final imageFile = await _picker.getImage(
source: ImageSource.gallery,
imageQuality: 70,
maxWidth: 480);
if (imageFile != null) {
Navigator.of(context).pop(imageFile);
}
},
),
],
);
});
BlocProvider.of<ProfileBloc>(context).add(UploadAvatarEvent(file));
};
},
listener: (_, state) {
if (state is ProfileLoadingState) {
BotToast.showLoading();
} else if (state is ProfileUploadAvatarSucceed) {
BotToast.closeAllLoading();
BotToast.showText(text: 'Avatar updated');
}
}
);
ProfileBloc.dart
#override
Stream<ProfileState> mapEventToState(
ProfileEvent event,
) async* {
if (event is UploadAvatarEvent) {
yield ProfileLoadingState();
await _apiProvider.updateUser(currentUser.id, payload);
final user = await _apiProvider.getUser(currentUser.id);
yield ProfileUploadAvatarSucceed(user);
}
}
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?
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
I have a widget named GiftGrid that shows a placeholder Text widget when it receives no items, but the test that checks for the specific case fails, even though it works fine in the app:
testWidgets('Null list shows placeholder', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
localizationsDelegates: [MyLocalizationsDelegate()],
home: GiftGrid(null)));
var a = tester.allWidgets.toList();
expect(find.text("PROPER_QUADRUPLE_CHECKED_STRING"), findsOneWidget);
});
where GiftGrid is defined as follows:
return (widget.giftList?.isEmpty ?? true)
? Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(MyLocalizations.of(context).stringThatTranslatesToSameAsTest),
),
) : StaggeredGridBuilder(...);
The debugger shows the contents of a as follows (sorry for the screenshot, I couldn't find a better way to show it):
EDIT: GifGrid receives a List as the parameter and the following test passes:
testWidgets('Gift shows thumbnail if available', (WidgetTester tester) async {
var gift = GiftModel()
..images = [
"OTHER_IMG_LINK"
]
..thumbnail =
"IMG_LINK";
// Build our app and trigger a frame.
await tester.pumpWidget(MaterialApp(
home: GiftGrid([gift]),
));
expect(find.byWidgetPredicate((widget) {
return widget is TransitionToImage &&
(widget.image as AdvancedNetworkImage).url ==
"IMG_LINK";
}), findsOneWidget);
});
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());
}
},
);