👻

Flutter バックグラウンドで RefreshIndicator を消そうとすると消えずに残る

2020/10/01に公開

Flutter でいわゆる「引っ張って更新」の機能を提供してくれる RefreshIndicator という Widget があります。

onRefresh で更新の処理を行い、その Future が完了するとインジケータが非表示になります。

ところが、この Future が完了したタイミングでアプリがバックグラウンドにいると、アプリがフォアグラウンドに戻った時にも、インジケータが消えずに残ったままとなります。

Issue として報告済み: https://github.com/flutter/flutter/issues/14619

ワークアラウンドとして、以下のような RefreshIndicator をラップした Widget を作りました。

import 'dart:async';

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

class MyRefreshIndicator extends StatefulWidget {
  const MyRefreshIndicator({
    Key key,
     this.child,
    this.displacement: 40.0,
     this.onRefresh,
    this.color,
    this.backgroundColor,
    this.notificationPredicate: defaultScrollNotificationPredicate,
  }) : assert(child != null),
       assert(onRefresh != null),
       assert(notificationPredicate != null),
       super(key: key);
  
  final Widget child;

  final double displacement;

  final RefreshCallback onRefresh;

  final Color color;

  final Color backgroundColor;

  final ScrollNotificationPredicate notificationPredicate;
  
  
  State<StatefulWidget> createState() {
    return new MyRefreshIndicatorState();
  }
}

class MyRefreshIndicatorState extends State<MyRefreshIndicator> with WidgetsBindingObserver {
  Completer<Null> completer;
  bool foreground = true;
  
  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }
  
  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
  
  
  void didChangeAppLifecycleState(AppLifecycleState state) {
    foreground = (state == AppLifecycleState.resumed);
    if (foreground && completer != null) {
      //debugPrint("complete on didChangeAppLifecycleState");
      completer.complete();
      completer = null;
    }
  }
  
  
  Widget build(BuildContext context) {
    return new RefreshIndicator(
      child: widget.child,
      onRefresh: _onRefresh,
      displacement: widget.displacement,
      color: widget.color,
      backgroundColor: widget.backgroundColor,
      notificationPredicate: widget.notificationPredicate,
    );
  }
  
  Future<Null> _onRefresh() {
    final Completer<Null> completer = new Completer<Null>();
    widget.onRefresh().then((_) {
      if (foreground) {
        //debugPrint("complete on original future");
        completer.complete();
      } else {
        this.completer = completer;
      }
    });
    return completer.future;
  }
}

ポイントとしては、実際の処理の Future が完了したタイミングで、アプリがフォアグラウンドにいるかどうか確認して、いないのであれば complete せずに待っておき、フォアグラウンドになった時点で complete しています。これにより、アプリがフォアグラウンドにいるタイミングでインジケータを消す処理が行われ、期待通りに動作します。

この記事はQiitaの記事をエクスポートしたものです

Discussion

ログインするとコメントできます