可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I've got a Flutter app with 2 tabs: one that manages and receives a continuous flow of data, the other tab displays the data as it comes in.
How do I pass the data from the first tab to the second? Most of the post I see are about passing data between parent and child, not child to child.
Would I use GlobalKey
? is there a better option?
This is the main build function:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('some text'),
bottom: TabBar(
tabs: tabs,
controller: _tabController,
),
),
body: TabBarView(
controller: _tabController,
children: [
InputManagment(),
InfiniteListView(),
],
),
);
}
回答1:
In this case, it is recommended to use InheritedWidget
.
The documentation for InheritedWidget
is very comprehensive, including a video from the Flutter team.
First of all, you probably want to create a class that holds the data you want to share.
import 'dart:async';
class MyInheritedWidgetData {
var sharedData;
int someCount;
String someMessage;
final StreamController _streamController = StreamController.broadcast();
Stream get stream => _streamController.stream;
Sink get sink => _streamController.sink;
}
I just added a bunch of variables to this class. You can populate it with whatever you want.
Now, you also want to have an InheritedWidget
that holds this data class.
class MyInheritedWidget extends InheritedWidget {
final MyInheritedWidgetData data;
MyInheritedWidget({
Key key,
@required Widget child,
}) : assert(child != null),
data = MyInheritedWidgetData(),
super(key: key, child: child);
static MyInheritedWidgetData of(BuildContext context) => (context.inheritFromWidgetOfExactType(MyInheritedWidget) as MyInheritedWidget).data;
@override
bool updateShouldNotify(MyInheritedWidget old) => false;
}
You need to place this MyInheritedWidget
at the top of your widget tree or at least above parent widget you talked about. The following is supposed to illustrate the necessary widget hierarchy.
MyInheritedWidget
TabBarView
InputManagment
InfiniteListView
// in your build function this would be `body: MyInheritedWidget(child: TabBarView(...))`
Now, you can simply access your data class by using MyInheritedWidget.of(context)
in any of your child widgets.
You might want to consider using streams to send and listen to the "flow of data" continuously. However, that would also simply be part of the data class. To give you an idea, I included the stream variable in the example data class. You would add data using MyInheritedWidget.of(context).sink.add(..)
and supply your stream to a StreamBuilder
using MyInheritedWidget.of(context).stream
.
These are all just examples to explain what is needed to share data between widgets. You can read through the documentation for more information and more advanced use cases.
回答2:
I believe the Provider is the recommended method for managing state in Flutter Application and was presented at Google IO and is at the top of the stack for state management in the Flutter Documentation on State Management
- Video from GoogleI/O Pragmatic State Management in Flutter (Google I/O'19)
service as my provider...
import 'dart:collection';
import 'package:flutter/material.dart';
class Item {
String name;
num price;
Item(this.name, this.price);
}
class CartModel extends ChangeNotifier {
/// Internal, private state of the cart.
final List<Item> _items = [];
/// An unmodifiable view of the items in the cart.
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
/// The current total price of all items (assuming all items cost $42).
/// int get totalPrice => _items.length * 42;
/// Adds [item] to cart. This is the only way to modify the cart from outside.
void add(Item item) {
_items.add(item);
// This call tells the widgets that are listening to this model to rebuild.
notifyListeners();
}
}
Setting up Access to State*
void main() => runApp(
ChangeNotifierProvider<CartModel>(
child: TabBarDemo(),
builder: (BuildContext context) {
return CartModel();
},
),
);
Accessing State from top level to display count in tab title
class TabBarDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
var count = Provider.of<CartModel>(context).items.length;
Accessing State from first tab when adding item to the cart
RaisedButton(
child: Text("Add Item"),
onPressed: () async {
final form = _formKey.currentState;
form.save();
if (form.validate()) {
Provider.of<CartModel>(context)
.add(new Item(_name, num.parse(_price)));
} else {
print('validate failed');
}
_formKey.currentState.reset();
})
See the complete example here: https://github.com/aaronksaunders/flutter_simple_tabs, this code is based on the Flutter Documentation Example
回答3:
I found it easy using good-ole stateful widget. There exists a first-class function on InputManagement that takes the data and builds the home page. It's important to note that InputManagement should be calling this function responsibly because it rebuilds the page.
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tabbar Answer',
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
TabController tabController;
List<dynamic> data = [];
@override
void initState() {
super.initState();
tabController = TabController(length: 2, vsync: this);
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: Text("Tab Delegate")),
body: TabBarView(children: [InputManagement(data: data, delegate: (newData) => setState(() { data = newData; })), InfiniteListView(data: data)], controller: tabController),
bottomNavigationBar: TabBar(controller: tabController, tabs: [Icon(Icons.mail, color: Colors.black), Icon(Icons.view_agenda, color: Colors.black,)])
);
}
class InputManagement extends StatelessWidget {
List<dynamic> data;
void Function(dynamic) delegate;
InputManagement({this.data, this.delegate});
add(dynamic dataItem) {
data.add(dataItem);
delegate(data);
}
@override
Widget build(BuildContext context) => Center(child: FloatingActionButton(onPressed: () => add(data.isEmpty ? 0 : data.last + 1), child: Icon(Icons.add)));
}
class InfiniteListView extends StatelessWidget {
List<dynamic> data = [];
InfiniteListView({this.data});
@override
Widget build(BuildContext context) => ListView.builder(itemBuilder: (context, index) => Container(height: 100, width: MediaQuery.of(context).size.width, alignment: Alignment.center, child: Text(data[index].toString(), style: Theme.of(context).textTheme.title.copyWith(color: Colors.black),)), itemCount: data.length);
}