Search code examples
darttimer

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};
}

Solution

  • 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).