Flutter

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

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,
@required this.child,
this.displacement: 40.0,
@required 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;

@override
State<StatefulWidget> createState() {
return new MyRefreshIndicatorState();
}
}

class MyRefreshIndicatorState extends State<MyRefreshIndicator> with WidgetsBindingObserver {
Completer<Null> completer;
bool foreground = true;

@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
foreground = (state == AppLifecycleState.resumed);
if (foreground && completer != null) {
//debugPrint("complete on didChangeAppLifecycleState");
completer.complete();
completer = null;
}
}

@override
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 しています。これにより、アプリがフォアグラウンドにいるタイミングでインジケータを消す処理が行われ、期待通りに動作します。