Проблема в том, что метод builder
FutureBuilder вызывается каждый раз, когда изменяется AsyncSnapshot (и изначально моментальный снимок не имеет данных). Следовательно, в первые несколько раз, когда вызывается builder
, userName
будет иметь значение null, что даст вам эту ошибку; но через какой-то момент имя пользователя будет получено, и при вызове функции построителя вы увидите имя пользователя на экране правильно.
Идиоматический c способ использования FutureBuilder следующий:
FutureBuilder(
future: myFuture,
builder: (context, AsyncSnapshot snapshot) {
// Try adding this print to your code to see when this method is being executed!
print('Building with snapshot = $snapshot');
if (snapshot.hasData) {
// return widget with data - in your case, userScaffold
}
else if (snapshot.hasError) {
// return widget informing of error
}
else {
// still loading, return loading widget (for example, a CircularProgressIndicator)
}
},
);
Итак, изначально функция построителя будет вызываться со снимком без данных (ветвь «else»). В этом случае вы, вероятно, захотите показать виджет загрузки. Затем, через некоторое время, Future завершается, и вызывается функция построителя со снимком, имеющим либо данные, либо некоторую ошибку.
Еще одна важная вещь в вашем коде - это то, что ваша функция _infoInit
на самом деле не вернуть что-нибудь. Итак, на самом деле ваш FutureBuilder не использует данные из AsyncSnapshot (что означает, что приведенный выше фрагмент на самом деле не будет работать, поскольку snapshot.hasData
никогда не будет истинным). С FutureBuilder вы обычно хотите создать виджет, используя данные, возвращаемые AsyncSnapshot. Вместо этого в вашем коде происходит следующее:
- FutureBuilder создан. Это вызывает
_infoInit()
, который запускает выборку данных из Firebase; вызывается метод построения - FutureBuilder. Он пытается использовать
userName
, но он имеет значение null, поэтому Flutter показывает неудачное утверждение; _infoInit()
извлекает все данные и возвращает Future (это будущее возвращается автоматически из-за предложения async
в подпись метода; однако без предложения return
он фактически не возвращает никаких данных). Но несмотря на то, что Future не имеет никаких данных, 3 переменные в состоянии (включая userName
) были обновлены и теперь содержат некоторые данные. - Поскольку будущее, переданное FutureBuilder, завершено, метод построителя вызывается снова. На этот раз в
userName
есть данные, поэтому он строится правильно.
Можно писать код, как вы, но в этом случае вам не нужно использовать FutureBuilder. Вы можете просто вызвать _infoInit()
из метода initState()
виджета (initState - это метод, вызываемый один раз при первом построении State), а после получения данных вызвать setState (). Вот как это будет выглядеть:
class UserPage extends StatefulWidget {
@override
UserPageState createState() => UserPageState();
}
class UserPageState extends State<UserPage> {
String userName;
String userEmail;
String collegeName;
bool loadingData = true;
@override
void initState() {
_infoInit();
}
// This can be void now, since we're changing the state, rather than returning a Future
void _infoInit() async {
String userName = await HelperFunctions.getUserNamePreference();
String userEmail = await HelperFunctions.getUserEmailPreference();
String collegeName = await HelperFunctions.getUserCollegePreference();
setState(() {
// In theory, we could have just updated the state variables above, but the
// recommended practice is to update state variables inside setState.
this.userName = userName;
this.userEmail = userEmail;
this.collegeName = collegeName;
loadingData = false;
});
}
Widget userScaffold(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
title: Text(userName),
backgroundColor: Colors.lightBlue,
),
body: Center(child: Text("This is User Page")));
}
@override
Widget build(BuildContext context) {
if (loadingData) {
// We don't have the data yet, so return a widget to indicate some loading state
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return userScaffold(context);
}
}
Приведенный выше фрагмент не обрабатывает ошибки при выборке данных, что вы, вероятно, захотите сделать. И для этого вы можете использовать флаг под названием 'hasError' или что-то в этом роде, что в конечном итоге даст очень похожий код на то, как написан метод "idiomati c" FutureBuilder builder
.
Оба являются действительные подходы; FutureBuilder, возможно, использует меньше кода (и может быть проще использовать, если остальная часть вашего кода уже использует Futures), но в конечном итоге это зависит от ваших предпочтений.