プロバイダーパッケージを使用してSnackBar経由でメッセージを表示する際に問題があります。私が得るエラーメッセージは:
VERBOSE-2:ui_Dart_state.cc(157)] Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
#0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.Dart:3508:9)
#1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.Dart:3522:6)
#2 Element.findAncestorStateOfType (package:flutter/src/widgets/framework.Dart:3641:12)
#3 Scaffold.of (package:flutter/src/material/scaffold.Dart:1313:42)
#4 LoginScreen.build.<anonymous closure>.<anonymous closure> (package:zvjs_app/screens/login_screen.Dart:74:38)
<asynchronous suspension>
#5 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.Dart:182<…>
ベローは私のコードです。必要なロジックの一部であるすべてのクラスだと思います。 Futureが「利用可能」にならない理由や、sigInメソッドのuser_log_in_provider.Dartでエラーが何を意味するのか理解できません。また、user_log_in_provider.Dartで確認できる変数_errorMessageを介してsigInメソッドからのerrorMessageを表示し、このメッセージがnullでないかどうかを確認しようとしました。この方法でコードは実行されますが、1つのメッセージが遅延していることが示されています。 eの場合最初のログインに失敗しました(間違ったメール形式)->メッセージは表示されません。 2回目のログインに失敗しました(間違ったパスワード)->間違ったメール形式のメッセージが表示されます。
main.Dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserLogIn.instance()),
ChangeNotifierProvider.value(value: Accommodations()),
],
child: MaterialApp(
title: 'ZVJS',
theme: ThemeData(
primarySwatch: Colors.blue,
buttonTheme: ButtonThemeData(
buttonColor: Colors.blue[300],
padding: EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
),
)),
home: MyHomePage(),
routes: {
RegistrationScreen.routeName: (context) => RegistrationScreen(),
MainScreen.routeName: (context) => MainScreen(),
LoginScreen.routeName: (context) => MyHomePage(),
},
),
);
}
}
class MyHomePage extends StatelessWidget {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Consumer<UserLogIn>(
builder: (context, user, _) {
switch (user.status) {
case Status.Uninitialized:
// return Splash();
case Status.Unauthenticated:
case Status.Authenticating:
return LoginScreen(
emailController: _emailController,
passwordController: _passwordController);
case Status.Authenticated:
return MainScreen();
default:
return ErrorPage();
}
},
);
}
}
login_screen.Dart
class LoginScreen extends StatelessWidget {
static const routeName = '/loginScreen';
final _emailController;
final _passwordController;
LoginScreen(
{@required TextEditingController emailController,
@required TextEditingController passwordController})
: this._emailController = emailController,
this._passwordController = passwordController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(Constants.logInPageTitle),
),
body: Provider.of<UserLogIn>(context).status == Status.Authenticating
? SpinnerCustom(Constants.loggingIn)
: Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: TextFieldCustom(
text: Constants.email,
controller: _emailController,
icon: Icon(Icons.email),
textInputType: TextInputType.emailAddress,
),
),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: TextFieldCustom(
text: Constants.password,
controller: _passwordController,
icon: Icon(Icons.lock),
textInputType: TextInputType.visiblePassword,
),
),
const SizedBox(height: 10),
Builder(
builder: (ctx) => ButtonCustom(
text: Constants.logIn,
onPressed: () async {
var provider = Provider.of<UserLogIn>(ctx, listen: false);
String message = await provider.signIn(
_emailController.text,
_passwordController.text);
if (message != null) {
Scaffold.of(ctx).showSnackBar(SnackBar(
content: Text(message),
));
}
},
),
),
],
),
),
),
);
}
}
user_log_in_provider.Dart
enum Status { Uninitialized, Authenticated, Authenticating, Unauthenticated }
class UserLogIn with ChangeNotifier {
FirebaseAuth _auth;
FirebaseUser _user;
Status _status = Status.Uninitialized;
String _errorMessage;
UserLogIn.instance() : _auth = FirebaseAuth.instance {
_auth.onAuthStateChanged.listen(_onAuthStateChanged);
}
Status get status => _status;
FirebaseUser get user => _user;
String get errorMessage => _errorMessage;
Future<String> signIn(String email, String password) async {
try {
_status = Status.Authenticating;
notifyListeners();
await _auth.signInWithEmailAndPassword(email: email, password: password);
return null;
} catch (e) {
_errorMessage = e.message;
print(_errorMessage);
_status = Status.Unauthenticated;
notifyListeners();
return e.message;
}
}
Future<void> _onAuthStateChanged(FirebaseUser firebaseUser) async {
if (firebaseUser == null) {
_status = Status.Unauthenticated;
} else {
_user = firebaseUser;
_status = Status.Authenticated;
}
notifyListeners();
}
}
次のコードが原因でエラーが発生します。
_ Scaffold.of(ctx).showSnackBar(SnackBar(
content: Text(message),
));
_
Scaffold.of(context)
は、それより上にないウィジェットツリーでスキャフォールドを検索しようとしています。
問題が発生する方法は次のとおりです。
String message = await provider.signIn(...);
Scaffold.of(ctx).showSnackbar(...)
が呼び出されると、存在しないウィジェットツリーでスキャフォールドを検索しようとしています。いくつかの解決策があります。それらの1つは、各ルートをラップするグローバルスキャフォールドを使用することです。その後、その足場キーを使用してスナックバーを表示できます。
これを行う方法は次のとおりです。
MaterialAppビルダーに足場を追加します。 必ずグローバルキーを使用してください
_final globalScaffoldKey = GlobalKey<ScaffoldState>();
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
...
child: MaterialApp(
builder: (context, child) {
return Scaffold(
key: globalScaffoldKey,
body: child,
);
},
...
_
次に、そのキーを使用して、グローバル関数を通じてスナックバーを表示できます。
_ void showSnackbar(String message) {
var currentScaffold = globalScaffoldKey.currentState;
currentScaffold.hideCurrentSnackBar(); // If there is a snackbar visible, hide it before the new one is shown.
currentScaffold.showSnackBar(SnackBar(content: Text(message)));
}
_
使用法は次のようになり、コードのどこからでも安全に呼び出すことができます。
_showSnackbar('My Snackbar Message')
_