Flutter - 从父级到子级调用函数(带参数)
Flutter - Calling a function (with parameters) from parent to child
我在从父级调用方法到子级时遇到问题,设置是我的脚手架有 2 个子级、一个 listView 和一个应用栏。在应用栏中,我有一个搜索栏,我想在搜索栏上搜索时更新列表视图。
我在流中看过 flutter 团队的视频,但我认为对于像这样的简单案例来说,这太过分了。我还发现了这个线程 和@ehsaneha 的响应,我认为这对我的用例非常有用。我可以使用 appBar 构造函数中的函数将数据从搜索栏获取到父级,然后将其从父级调用到 listView !我做到了,当你不给函数任何数据时它工作正常,但是一旦你需要参数(就像在这种情况下输入搜索栏的字符串)它就不再工作了。我有一条错误消息:
a value of type 'dynamic Function(dynamic)' can't be assigned to a variable of type 'dynamic Function()'
关于“= updateList”部分:
_ListViewState(ListViewController _controller) {
_controller.updateList = updateList;
}
我对此有点迷茫,我不确定我的方向是否正确,我在搜索中没有找到简单的答案。我来自 Angular,我找不到像 observable 这样简单的东西,我可以将它提供给我的 listView,以便它订阅它。
在此先感谢您的帮助!
编辑:
根据评论,我添加了更多代码,以便我们可以更详细地查看我的用例:
所以我有一个 homepage.dart 看起来像这样
final ListViewController listViewController = ListViewController();
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(setSearchString: setSearchString),
body: MyListView(controller: listViewController)
)
}
...
setSearchString(String searchString) {
listViewController.updateList();
}
在我的 listView 中,我已经设置了能够访问 updateList() 函数所需的一切,它看起来像这样:
class MyListView extends StatefulWidget {
final ListViewController controller;
MyListView({this.controller});
@override
_MyListViewState createState() => _MyListViewState(controller);
}
class _MyListViewState extends State<MyListView> {
_MyListViewState(ListViewController _controller) {
_controller.updateList = updateList;
}
...
updateList() {
print('In updateList !');
}
}
搜索栏部分只是使用构造函数中传递的函数将搜索文本获取到父级(homepage.dart)的情况,所以我认为不需要
所以这就是我所在的位置,它现在工作正常,但是一旦我想向 updateList() 添加一个参数,如 updateList(String text),它就会给我上面提到的错误。我希望我添加了足够的代码,如果不是这样请告诉我,我会尝试添加所需的代码!
所以首先让我向您展示一个如何使用您的方法解决此问题的示例 - 请同时阅读代码块中的注释 - 这些应该有助于更好地理解我在那里做了什么!
剧透:我们不想在 Flutter 中这样做,因为它会导致到处传递对象/函数,而且维护这样的代码会变得非常困难- 这个“问题”有很好的解决方案,总结在“状态管理”下。
我创建了一个 class 用于保存在搜索栏中输入的当前搜索查询:
class SearchModel {
String searchString = '';
}
然后我们有了我们的简约视图,我们在其中使用了 Scaffold 小部件和(就像在您的示例中一样)一个 AppBar 和一个 ListView:
class HomeView extends StatefulWidget {
@override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
SearchModel _searchModel = SearchModel();
_updateSearch(String searchQuery) {
/// The setState function is provided by StatefulWidget, every Widget we
/// create which extends StatefulWidget has access to this function. By calling
/// this function we essentially say Flutter we want the Widget (which is coupled
/// with this state of this StatefulWidget) that we want to rebuild (call the build
/// function again)
setState(() {
_searchModel.searchString = searchQuery;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
/// We pass down the _updateSearch function to our AppBar so when the user
/// changes the text in the TextField it will update the searchString in our
/// SearchModel object and call setState so we rebuild HomeViewState (which is
/// currently the root of our app, so everything gets rebuilded)
child: MyAppBar(
searchFunction: _updateSearch,
),
preferredSize: Size.fromHeight(kToolbarHeight)),
/// In MyListView, where we use the ListView internally to show the results, we
/// just pass down our SearchModel object where the searchString is maintained
/// so we can filter our list
body: MyListView(
searchModel: _searchModel,
),
);
}
}
现在是我们的 AppBar,用户可以在其中输入内容并搜索我们的列表:
class MyAppBar extends StatefulWidget {
/// This is how we declare a function type in Dart where we say which
/// kind of parameters (here String) it will use
final Function(String) searchFunction;
MyAppBar({this.searchFunction});
@override
_MyAppBarState createState() => _MyAppBarState();
}
class _MyAppBarState extends State<MyAppBar> {
@override
Widget build(BuildContext context) {
return AppBar(
title: TextField(
/// onChanged of TextField needs a function where we pass down
/// a String and do what we want, thats what the searchFunction is for!
onChanged: widget.searchFunction,
),
);
}
}
最后但同样重要的是 ListView 小部件,我们在其中显示一些元素,如果用户更改我们 AppBar 的 TextField 中的输入,我们希望过滤这些元素并仅显示与 searchQuery 匹配的元素:
class MyListView extends StatefulWidget {
final SearchModel searchModel;
MyListView({this.searchModel});
@override
_MyListViewState createState() => _MyListViewState();
}
class _MyListViewState extends State<MyListView> {
List<String> games = [
'Anno 1602',
'Final Fantasy 7',
'Final Fantasy 8',
'Dark Souls'
];
@override
Widget build(BuildContext context) {
return ListView(
/// Some minimalistic usage of functions which are usable on List objects:
/// I initialised a random List of game names as Strings and the first thing
/// I want to do is to filter out all the games which contain the same String
/// pattern like the searchQuery which the user entered - this is done with the
/// where function. The where function returns the filtered list, on this filtered
/// list i use the map function, which takes every object from this list and "maps"
/// it to whatever we want, here for every game String I want a ListTile Widget which
/// is being used for our ListView! At the end I have to call toList() since those
/// functions return so called Iterables instead of List object, "basically" the same
/// but different classes so we just change the Iterables to List
children: games
.where((game) => game
.toLowerCase()
.contains(widget.searchModel.searchString.toLowerCase()))
.map(
(game) => ListTile(
title: Text(game),
),
)
.toList(),
);
}
}
好吧,这行得通,但是 正如我在开头所说的那样,这样做会迫使您将对象/函数传递给几乎所有地方的每个 Widget!一旦你的应用程序变得足够大,你所有的小部件都会有几十个参数,你很快就会忘记你在哪里做了什么,维护、改进、扩展你的代码几乎是不可能的。
这就是为什么我们需要一种叫做状态管理的东西!尽管 Flutter 很新,至少与其他知名框架相比,社区提出了许多不同的状态管理解决方案/方法。您应该自己阅读以找出最适合您的解决方案——实际上很多都取决于个人喜好。由于我个人喜欢将 Provider(您可以单独使用它作为解决方案)与 MobX 一起使用,因此我将向您展示如何仅使用 Provider (https://pub.dev/packages/provider):[=19 来处理这个示例=]
首先是我们的 SearchModel,现在已 扩展 :
/// We extend our classes which are basically our "States" with ChangeNotifier
/// so we enable our class to notify all listeners about a change - you will see
/// why!
class SearchModel extends ChangeNotifier {
String searchString = '';
/// Now we don't update the searchString variable directly anymore, we use a
/// function because we need to call notifiyListeners every time we change something
/// where we want to notify everyone and all the listeners can react to this change!
updateSearchString(searchQuery) {
this.searchString = searchQuery;
notifyListeners();
}
}
现在我们的新 AppBar:
/// Our own Widgets MyAppBar and MyListView are not Stateless! We don't need the state
/// anymore (at least in this example) since we won't use setState anymore and tell
/// our Widgets to rebuild, but will make use of a Widget provided by the Provider
/// package which will rebuild itself dynamically! More later in MyListView
class MyAppBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AppBar(
title: TextField(
/// context.watch is also a function added through the Provider package where
/// we can access objects which have been provided by a Provider Widget (you
/// will see this in the HomeView implementation) and thats how we now pass
/// the function instead of passing it down to this Widget manually! Easier right?
onChanged: context.watch<SearchModel>().updateSearchString,
),
);
}
}
更新后的 ListView:
class MyListView extends StatelessWidget {
final List<String> games = [
'Anno 1602',
'Final Fantasy 7',
'Final Fantasy 8',
'Dark Souls'
];
@override
Widget build(BuildContext context) {
return ListView(
/// Just like in the MyAppBar implementation we can access our SearchModel object
/// directly by using context.watch instead of passing it down to our Widget!
/// again: very nice
/// The function itself hasn't been changed!
/// Since we are using the watch function on a object which extends ChangeNotifier,
/// every time it gets updated, this will get rebuilded automatically! Magic
children: games
.where((game) => game.toLowerCase().contains(
context.watch<SearchModel>().searchString.toLowerCase()))
.map(
(game) => ListTile(
title: Text(game),
),
)
.toList(),
);
}
}
现在我们的 HomeView 基本上是我们开始这个视图的地方:
/// Every Widget we created manually now is stateless since we don't manage any
/// state by ourself now, which reduces the boilerplate and makes accessing stuff easier!
/// Whats left: in our MyListView and MyAppBar Widgets we accessed the SearchModel
/// object with context.watch ... but how is this possible? Well, we need to provide
/// it somehow of course - thats where the Provider packages gets handy again!
class HomeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// The Provider package has several Provider Widgets, one is the ChangeNotifierProvider
/// which can be used to provide objects which extend ChangeNotifier, just what we need!
///
/// Some background: we have to remember that Flutter is basically a big tree. Usually we
/// use a MaterialApp Widget as the root Widget and after that everything else is being
/// passed down as a child which will result in a big tree. What we do here: As early as we
/// need it / want to we place our Provider Widget and "pass down" our Model which should be
/// accessible for every Widget down the tree. Every Widget which is now under this Provider
/// (in our case MyAppBar and MyListView) can access this object with context.watch and
/// work with it
return ChangeNotifierProvider(
create: (_) => SearchModel(),
builder: (context, _) => Scaffold(
appBar: PreferredSize(
child: MyAppBar(), preferredSize: Size.fromHeight(kToolbarHeight)),
body: MyListView(),
),
);
}
}
很多东西要读 - 抱歉拖了这么久,但我想确保向您展示问题所在,如何尝试您的方法解决问题,以及为什么学习和使用 Flutter 中的状态管理如此重要越早越好!我希望这能让您很好地理解为什么它如此重要和优秀,并且它使 Flutter 更加出色。
我在此示例中使用 Provider 的方式也只是使用此状态管理解决方案的众多方式之一 - 我强烈建议您在 Provider 本身的 pub.dev 站点上自行阅读我较早链接。有很多示例如何以及何时使用 Provider 的不同小部件/方法!
希望对您有所帮助! :)
我在从父级调用方法到子级时遇到问题,设置是我的脚手架有 2 个子级、一个 listView 和一个应用栏。在应用栏中,我有一个搜索栏,我想在搜索栏上搜索时更新列表视图。
我在流中看过 flutter 团队的视频,但我认为对于像这样的简单案例来说,这太过分了。我还发现了这个线程
a value of type 'dynamic Function(dynamic)' can't be assigned to a variable of type 'dynamic Function()'
关于“= updateList”部分:
_ListViewState(ListViewController _controller) {
_controller.updateList = updateList;
}
我对此有点迷茫,我不确定我的方向是否正确,我在搜索中没有找到简单的答案。我来自 Angular,我找不到像 observable 这样简单的东西,我可以将它提供给我的 listView,以便它订阅它。
在此先感谢您的帮助!
编辑:
根据评论,我添加了更多代码,以便我们可以更详细地查看我的用例:
所以我有一个 homepage.dart 看起来像这样
final ListViewController listViewController = ListViewController();
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(setSearchString: setSearchString),
body: MyListView(controller: listViewController)
)
}
...
setSearchString(String searchString) {
listViewController.updateList();
}
在我的 listView 中,我已经设置了能够访问 updateList() 函数所需的一切,它看起来像这样:
class MyListView extends StatefulWidget {
final ListViewController controller;
MyListView({this.controller});
@override
_MyListViewState createState() => _MyListViewState(controller);
}
class _MyListViewState extends State<MyListView> {
_MyListViewState(ListViewController _controller) {
_controller.updateList = updateList;
}
...
updateList() {
print('In updateList !');
}
}
搜索栏部分只是使用构造函数中传递的函数将搜索文本获取到父级(homepage.dart)的情况,所以我认为不需要
所以这就是我所在的位置,它现在工作正常,但是一旦我想向 updateList() 添加一个参数,如 updateList(String text),它就会给我上面提到的错误。我希望我添加了足够的代码,如果不是这样请告诉我,我会尝试添加所需的代码!
所以首先让我向您展示一个如何使用您的方法解决此问题的示例 - 请同时阅读代码块中的注释 - 这些应该有助于更好地理解我在那里做了什么!
剧透:我们不想在 Flutter 中这样做,因为它会导致到处传递对象/函数,而且维护这样的代码会变得非常困难- 这个“问题”有很好的解决方案,总结在“状态管理”下。
我创建了一个 class 用于保存在搜索栏中输入的当前搜索查询:
class SearchModel {
String searchString = '';
}
然后我们有了我们的简约视图,我们在其中使用了 Scaffold 小部件和(就像在您的示例中一样)一个 AppBar 和一个 ListView:
class HomeView extends StatefulWidget {
@override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
SearchModel _searchModel = SearchModel();
_updateSearch(String searchQuery) {
/// The setState function is provided by StatefulWidget, every Widget we
/// create which extends StatefulWidget has access to this function. By calling
/// this function we essentially say Flutter we want the Widget (which is coupled
/// with this state of this StatefulWidget) that we want to rebuild (call the build
/// function again)
setState(() {
_searchModel.searchString = searchQuery;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
/// We pass down the _updateSearch function to our AppBar so when the user
/// changes the text in the TextField it will update the searchString in our
/// SearchModel object and call setState so we rebuild HomeViewState (which is
/// currently the root of our app, so everything gets rebuilded)
child: MyAppBar(
searchFunction: _updateSearch,
),
preferredSize: Size.fromHeight(kToolbarHeight)),
/// In MyListView, where we use the ListView internally to show the results, we
/// just pass down our SearchModel object where the searchString is maintained
/// so we can filter our list
body: MyListView(
searchModel: _searchModel,
),
);
}
}
现在是我们的 AppBar,用户可以在其中输入内容并搜索我们的列表:
class MyAppBar extends StatefulWidget {
/// This is how we declare a function type in Dart where we say which
/// kind of parameters (here String) it will use
final Function(String) searchFunction;
MyAppBar({this.searchFunction});
@override
_MyAppBarState createState() => _MyAppBarState();
}
class _MyAppBarState extends State<MyAppBar> {
@override
Widget build(BuildContext context) {
return AppBar(
title: TextField(
/// onChanged of TextField needs a function where we pass down
/// a String and do what we want, thats what the searchFunction is for!
onChanged: widget.searchFunction,
),
);
}
}
最后但同样重要的是 ListView 小部件,我们在其中显示一些元素,如果用户更改我们 AppBar 的 TextField 中的输入,我们希望过滤这些元素并仅显示与 searchQuery 匹配的元素:
class MyListView extends StatefulWidget {
final SearchModel searchModel;
MyListView({this.searchModel});
@override
_MyListViewState createState() => _MyListViewState();
}
class _MyListViewState extends State<MyListView> {
List<String> games = [
'Anno 1602',
'Final Fantasy 7',
'Final Fantasy 8',
'Dark Souls'
];
@override
Widget build(BuildContext context) {
return ListView(
/// Some minimalistic usage of functions which are usable on List objects:
/// I initialised a random List of game names as Strings and the first thing
/// I want to do is to filter out all the games which contain the same String
/// pattern like the searchQuery which the user entered - this is done with the
/// where function. The where function returns the filtered list, on this filtered
/// list i use the map function, which takes every object from this list and "maps"
/// it to whatever we want, here for every game String I want a ListTile Widget which
/// is being used for our ListView! At the end I have to call toList() since those
/// functions return so called Iterables instead of List object, "basically" the same
/// but different classes so we just change the Iterables to List
children: games
.where((game) => game
.toLowerCase()
.contains(widget.searchModel.searchString.toLowerCase()))
.map(
(game) => ListTile(
title: Text(game),
),
)
.toList(),
);
}
}
好吧,这行得通,但是 正如我在开头所说的那样,这样做会迫使您将对象/函数传递给几乎所有地方的每个 Widget!一旦你的应用程序变得足够大,你所有的小部件都会有几十个参数,你很快就会忘记你在哪里做了什么,维护、改进、扩展你的代码几乎是不可能的。
这就是为什么我们需要一种叫做状态管理的东西!尽管 Flutter 很新,至少与其他知名框架相比,社区提出了许多不同的状态管理解决方案/方法。您应该自己阅读以找出最适合您的解决方案——实际上很多都取决于个人喜好。由于我个人喜欢将 Provider(您可以单独使用它作为解决方案)与 MobX 一起使用,因此我将向您展示如何仅使用 Provider (https://pub.dev/packages/provider):[=19 来处理这个示例=]
首先是我们的 SearchModel,现在已 扩展 :
/// We extend our classes which are basically our "States" with ChangeNotifier
/// so we enable our class to notify all listeners about a change - you will see
/// why!
class SearchModel extends ChangeNotifier {
String searchString = '';
/// Now we don't update the searchString variable directly anymore, we use a
/// function because we need to call notifiyListeners every time we change something
/// where we want to notify everyone and all the listeners can react to this change!
updateSearchString(searchQuery) {
this.searchString = searchQuery;
notifyListeners();
}
}
现在我们的新 AppBar:
/// Our own Widgets MyAppBar and MyListView are not Stateless! We don't need the state
/// anymore (at least in this example) since we won't use setState anymore and tell
/// our Widgets to rebuild, but will make use of a Widget provided by the Provider
/// package which will rebuild itself dynamically! More later in MyListView
class MyAppBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AppBar(
title: TextField(
/// context.watch is also a function added through the Provider package where
/// we can access objects which have been provided by a Provider Widget (you
/// will see this in the HomeView implementation) and thats how we now pass
/// the function instead of passing it down to this Widget manually! Easier right?
onChanged: context.watch<SearchModel>().updateSearchString,
),
);
}
}
更新后的 ListView:
class MyListView extends StatelessWidget {
final List<String> games = [
'Anno 1602',
'Final Fantasy 7',
'Final Fantasy 8',
'Dark Souls'
];
@override
Widget build(BuildContext context) {
return ListView(
/// Just like in the MyAppBar implementation we can access our SearchModel object
/// directly by using context.watch instead of passing it down to our Widget!
/// again: very nice
/// The function itself hasn't been changed!
/// Since we are using the watch function on a object which extends ChangeNotifier,
/// every time it gets updated, this will get rebuilded automatically! Magic
children: games
.where((game) => game.toLowerCase().contains(
context.watch<SearchModel>().searchString.toLowerCase()))
.map(
(game) => ListTile(
title: Text(game),
),
)
.toList(),
);
}
}
现在我们的 HomeView 基本上是我们开始这个视图的地方:
/// Every Widget we created manually now is stateless since we don't manage any
/// state by ourself now, which reduces the boilerplate and makes accessing stuff easier!
/// Whats left: in our MyListView and MyAppBar Widgets we accessed the SearchModel
/// object with context.watch ... but how is this possible? Well, we need to provide
/// it somehow of course - thats where the Provider packages gets handy again!
class HomeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// The Provider package has several Provider Widgets, one is the ChangeNotifierProvider
/// which can be used to provide objects which extend ChangeNotifier, just what we need!
///
/// Some background: we have to remember that Flutter is basically a big tree. Usually we
/// use a MaterialApp Widget as the root Widget and after that everything else is being
/// passed down as a child which will result in a big tree. What we do here: As early as we
/// need it / want to we place our Provider Widget and "pass down" our Model which should be
/// accessible for every Widget down the tree. Every Widget which is now under this Provider
/// (in our case MyAppBar and MyListView) can access this object with context.watch and
/// work with it
return ChangeNotifierProvider(
create: (_) => SearchModel(),
builder: (context, _) => Scaffold(
appBar: PreferredSize(
child: MyAppBar(), preferredSize: Size.fromHeight(kToolbarHeight)),
body: MyListView(),
),
);
}
}
很多东西要读 - 抱歉拖了这么久,但我想确保向您展示问题所在,如何尝试您的方法解决问题,以及为什么学习和使用 Flutter 中的状态管理如此重要越早越好!我希望这能让您很好地理解为什么它如此重要和优秀,并且它使 Flutter 更加出色。
我在此示例中使用 Provider 的方式也只是使用此状态管理解决方案的众多方式之一 - 我强烈建议您在 Provider 本身的 pub.dev 站点上自行阅读我较早链接。有很多示例如何以及何时使用 Provider 的不同小部件/方法!
希望对您有所帮助! :)