Long time no see, the article has not updated for a period of time, recently has been indulging in the work can not dial 😂. Last week, in response to the company’s call and the infection of the Flutter presentation at the last Google conference, the company plans to implement the company’s new project with Flutter technology. It took me a few days to get familiar with the basic syntax and structure of Flutter, and then I started to build the project and develop the function of the basic modules. After all, only through practical practice can I accelerate the familiarity and “digestion” of the new technology.

When it comes to captchas, the usual way to do this might be with a timer. We should almost certainly check the Flutter website to see if there is a timer component. Sure enough, a Timer component is provided to implement the countdown. Let’s take a look at the official description of it:

A count-down timer that can be configured to fire once or repeatedly.

That is, it is a timer that supports one or more periodic triggers. First, let’s familiarize ourselves with the basic usage in both scenarios.

A single trigger

The common scenario in this case is to use it as a delay timer. We do not receive the progress of the countdown, but only a callback notification when the countdown is over. For example, take a look at the sample code provided by Flutter officials:

const timeout = const Duration(seconds: 3);
const ms = const Duration(milliseconds: 1);

startTimeout([int milliseconds]) {
  var duration = milliseconds == null ? timeout : ms * milliseconds;
  return newTimer(duration, handleTimeout); }...void handleTimeout() {  // callback function. }Copy the code

As you can see from the code above, a Timer created with new Timer(Duration Duration, void callback()) provides a callback method to handle the operation after the Timer expires. This clearly doesn’t meet the need for our captchas to display progress in real time, so let’s see how the Timer repeatedly triggers callbacks.

Periodic trigger

Periodic timing callbacks are common, and anything that involves timing can be done without it: we need both to be told when the timing ends and to monitor the timing progress in real time. This is exactly what we originally wanted for captchas. The Dart-Async package also gives us the timer.periodic constructor to create a Timer that can be called back repeatedly:

Timer.periodic(
	Duration duration,
	void callback(
		Timer timer
	)
)
Copy the code

In addition, the callback parameter is described as follows:

The callback is invoked repeatedly with duration intervals until canceled with the cancel function.

The callback method is called several times over time (duration) until the timer.cancel method is called. It is worth noting that the timer that is periodically called back does not “end the timer”, or it does not automatically end the timer, so we need to manually count the time and cancel the timer in time. How do you do that? Having said so much, let’s get into the topic of this article: verification code countdown implementation 😂. In fact, the implementation code of this function is very simple, here is a direct paste of the code:

	Timer _countDownTimer;
  int _currentTime = 60;
  bool get_isTimeCountingDown => _currentTime ! =60;

	void _startTimeCountDown() {
    if(_countDownTimer ! =null) {
      timer.cancel();
      timer = null;
    }
    _countDownTimer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (timer.tick == 60) {
        _currentTime = 60;
        _countDownTimer.cancel();
        _countDownTimer = null;
      } else {
        _currentTime--;
      }
      setState(() {
      });
    });
  }

	@override
  voiddispose() { _countDownTimer? .cancel(); _countDownTimer =null;
    super.dispose();
  }
Copy the code

We can use timer.tick to get the current (incremental) progress of the time, and _currentTime to mark the progress value of the time. The rest of the logic should be easier to understand.

What? You think this is the end of it? Haha, of course not, if the countdown function needs to be used in many different scenarios in our project, then we should encapsulate the countdown function. Besides, it is not friendly to refresh the whole view tree by displaying the countdown progress in real time through setState mode.

Countdown package

At present, the Flutter state management scheme presents a “hundred schools of contention”, and I prefer Provider to manage the state. Use a Provider to encapsulate the countdown function as a component:

import 'dart:async';
import 'package:flutter/material.dart';

/// Timer component
class CountDownTimeModel extends ChangeNotifier {
  final int timeMax;
  final int interval;
  int _time;
  Timer _timer;

  int get currentTime => _time;

  bool get isFinish => _time == timeMax;

  CountDownTimeModel(this.timeMax, this.interval) {
    _time = timeMax;
  }

  void startCountDown() {
    if(_timer ! =null) {
      _timer.cancel();
      _timer = null;
    }
    _timer = Timer.periodic(Duration(seconds: interval), (timer) {
      if (timer.tick == timeMax) {
        _time = timeMax;
        timer.cancel();
        timer = null;
      } else {
        _time--;
      }
      notifyListeners();
    });
  }
  
  void cancel() {
    if(_timer ! =null) {
      _timer.cancel();
      _timer = null; }}@override
  void dispose() {
    _timer.cancel();
    _timer = null;
    super.dispose(); }}Copy the code

How do you use it? If you are familiar with Provider usage, you need not say more. Here is an example of the following effect:

Click “Obtain verification code” button to start the 60-second countdown service, and the button should be in unclickable state during the countdown process. After the countdown, the button will resume to click state. The specific code is as follows:

class _LoginPageState extends State<TestPage> {
  @override
  Widget build(BuildContext context) {
    final logo = Hero(
      tag: 'hero',
      child: CircleAvatar(
        backgroundColor: Colors.transparent,
        radius: 48.0,
        child: Image.asset(ImageAssets.holder_logo),
      ),
    );

    final email = TextFormField(
      keyboardType: TextInputType.emailAddress,
      autofocus: false,
      initialValue: '[email protected]',
      decoration: InputDecoration(
        hintText: 'Email',
        contentPadding: EdgeInsets.fromLTRB(20.0.10.0.20.0.10.0),
        border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0))));final password = TextFormField(
      autofocus: false,
      initialValue: 'some password',
      obscureText: true,
      decoration: InputDecoration(
        hintText: 'Password',
        contentPadding: EdgeInsets.fromLTRB(20.0.10.0.20.0.10.0),
        border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0))));final loginButton = Padding(
      padding: EdgeInsets.symmetric(vertical: 16.0),
      child: RaisedButton(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(24),
        ),
        onPressed: () {
        },
        padding: EdgeInsets.all(12),
        color: Colors.lightBlueAccent,
        child: Text('Log In', style: TextStyle(color: Colors.white)),
      ),
    );

    final forgotLabel = FlatButton(
      child: Text(
        'Forgot password? ',
        style: TextStyle(color: Colors.black54),
      ),
      onPressed: () {},
    );

    return Scaffold(
      backgroundColor: Colors.white,
      body: Center(
        child: ListView(
          shrinkWrap: true,
          padding: EdgeInsets.only(left: 24.0, right: 24.0),
          children: <Widget>[
            logo,
            SizedBox(height: 48.0),
            email,
            SizedBox(height: 8.0),
            ScreenUtils.verticalSpace(2),
            Stack(
              children: <Widget>[
                password,

                PartialConsumeComponent<CountDownTimeModel>(
                  model: CountDownTimeModel(60.1),
                  builder: (context, model, _) => Positioned(
                    right: 10,
                    bottom: 1,
                    top: 1,
                    child: FlatButton(
                        disabledColor: Colors.grey.withOpacity(0.36),
                        color: Colors.white70.withOpacity(0.7), onPressed: ! model.isFinish ?null : () {
                          model.startCountDown();
                        },
                        child: Text(
                          model.isFinish ? 'Get captcha' : model.currentTime.toString()+'Retrieve after seconds',
                          style: TextStyle(color: model.isFinish ? Colors.lightBlueAccent : Colors.white),
                        )
                    ),
                  ),
                ),
              ],
            ),
            SizedBox(height: 24.0), loginButton, forgotLabel ], ), ), ); }}Copy the code

Here, we determine whether the countdown is in progress by defining the isFinish field in CountDownTimeModel to handle the various states of the button (color, click status, text content, and so on). Here, to facilitate the management of the current page state, I have wrapped a single common consumer component:

class PartialConsumeComponent<T extends ChangeNotifier> extends StatefulWidget {

  final T model;
  final Widget child;
  final ValueWidgetBuilder<T> builder;

  PartialConsumeComponent({
    Key key,
    @required this.model,
    @required this.builder,
    this.child
  }) : super(key: key);

  @override
  _PartialConsumeComponentState<T> createState() => _PartialConsumeComponentState<T>();

}

class _PartialConsumeComponentState<T extends ChangeNotifier> extends State<PartialConsumeComponent<T>> {

  @override
  Widget build(BuildContext context) {
    returnChangeNotifierProvider<T>.value( value: widget.model, child: Consumer<T>( builder: widget.builder, child: widget.child, ), ); }}Copy the code

The last

I have just been in touch with Flutter for about two weeks. Some details may be neglected for the sake of the development schedule. Please correct me if there are any imprecisions or omissions. In addition, please look forward to more articles on Android and Flutter in the future.

reference

  • Official Documentation of Flutter
  • Provider
  • Flutter | state management guide – the Provider