# InheritedWidget vs Provider
### InheritedWidget
是一個可以讓資料可以在 widget tree 中向下傳遞、共享的 widget,因為他是 widget,所以可以在任意節點中插入此 `InheritedWidget`,在這個底下的所有 widget 都可以訪問到這個 `InheritedWidget` 內的資料。
* 通過 `context.dependOnInheritedWidgetOfExactType<T>` 取得父節點中相同類型的最近節點並返回,如果找不到相同類型的節點,返回 null。
* `updateShouldNotify` 定義資料改動的時機並發送通知給觀察者
#### Usage
* 定義 `ShareDataWidget` 並繼承 `InheritedWidget`。
* 定義靜態方法 `of` 用來查找此物件。
* 在 `oldWidget.count != count`,count 數量改變時時發送通知。
```
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class ShareDataWidget extends InheritedWidget {
const ShareDataWidget({
Key? key,
required this.selectedColorIndex,
required this.selectedSizeIndex,
required Widget child,
}) : super(key: key, child: child);
final int selectedColorIndex;
final int selectedSizeIndex;
static ShareDataWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
}
@override
bool updateShouldNotify(ShareDataWidget oldWidget) {
return oldWidget.selectedColorIndex != selectedColorIndex;
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int selectedColorIndex = -1;
int selectedSizeIndex = -1;
@override
Widget build(BuildContext context) {
return ShareDataWidget(
selectedColorIndex: selectedColorIndex,
selectedSizeIndex: selectedSizeIndex,
child: Scaffold(
body: SizedBox(
width: double.infinity,
height: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text('顏色', style: TextStyle(color: Colors.black, fontSize: 16)),
const SizedBox(width: 10),
Container(color: const Color(0x19333333), height: 18, width: 1),
const SizedBox(width: 5),
SingleSelectColor(
colorList: const [Colors.green, Colors.deepOrange],
onSelect: (selectedColorIndex) {
setState(() {
this.selectedColorIndex = selectedColorIndex;
selectedSizeIndex = -1;
});
},
),
],
),
const SizedBox(height: 15),
Row(
children: [
const Text('尺寸', style: TextStyle(color: Colors.black, fontSize: 16)),
const SizedBox(width: 10),
Container(color: const Color(0x19333333), height: 18, width: 1),
const SizedBox(width: 10),
SingleSelectSize(
sizeList: const ['S', 'M', 'L'],
onSelect: (index) {
setState(() {
selectedSizeIndex = index;
});
},
),
],
),
],
),
],
),
),
),
);
}
}
class SingleSelectColor extends StatelessWidget {
final List<MaterialColor> colorList;
final Function(int) onSelect;
const SingleSelectColor({super.key, required this.colorList, required this.onSelect});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List<Widget>.generate(
colorList.length,
(int index) {
return Row(
children: [
OutlinedButton(
onPressed: () {
onSelect(index);
},
style: ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
minimumSize: MaterialStateProperty.all(const Size(40, 40)),
padding: MaterialStateProperty.all<EdgeInsets>(const EdgeInsets.all(0)),
side: MaterialStateProperty.all(BorderSide(color: getColor(context, index), width: 1.0, style: BorderStyle.solid)),
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
),
child: Container(color: colorList[index], height: 18, width: 18)),
],
);
},
),
);
}
Color getColor(BuildContext context, int index) {
if (ShareDataWidget.of(context) == null) {
return Colors.transparent;
} else {
return ShareDataWidget.of(context)!.selectedColorIndex == index ? Colors.black : Colors.transparent;
}
}
}
class SingleSelectSize extends StatelessWidget {
final List<String> sizeList;
final Function(int) onSelect;
final grey = const Color.fromARGB(255, 87, 99, 108);
final white = Colors.white;
const SingleSelectSize({super.key, required this.sizeList, required this.onSelect});
@override
Widget build(BuildContext context) {
Color getBackgroundColor(int index) {
if (ShareDataWidget.of(context) == null) {
return white;
} else {
return ShareDataWidget.of(context)!.selectedSizeIndex == index ? white : grey;
}
}
Color getTextColor(int index) {
if (ShareDataWidget.of(context) == null) {
return white;
} else {
return ShareDataWidget.of(context)!.selectedSizeIndex == index ? grey : white;
}
}
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List<Widget>.generate(
sizeList.length,
(int index) {
return Row(
children: [
ElevatedButton(
onPressed: () {
onSelect(index);
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.only(top: 3, bottom: 3, left: 10, right: 10),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: getBackgroundColor(index),
),
child: Text(sizeList[index], style: TextStyle(color: getTextColor(index), fontSize: 12)),
),
const SizedBox(width: 10),
],
);
},
),
);
}
}
```
### Provider
類似觀察者模式,具有觀察者和被觀察者,widget 透過觀察者模式觀察數據,一旦資料來源變動,widget 會自動重新渲染,更新介面。
#### Usage
* 定義 `MainViewModel` 並繼承 `ChangeNotifier`,裡面存在需要變更的數據,在資料變更後呼叫 `notifyListeners()` 發送通知。
* 使用 `ChangeNotifierProvider` 創建 `MainViewModel`。
* 使用 `Consumer` 取得 `MainViewModel` 內的資料。
```
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'main_view_model.dart';
void main() {
runApp(ChangeNotifierProvider(
create: (context) => MainViewModel(),
child: const MyApp(),
));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SizedBox(
width: double.infinity,
height: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text('顏色', style: TextStyle(color: Colors.black, fontSize: 16)),
const SizedBox(width: 10),
Container(color: const Color(0x19333333), height: 18, width: 1),
const SizedBox(width: 5),
const SingleSelectColor(
colorList: [Colors.green, Colors.deepOrange],
),
],
),
const SizedBox(height: 15),
Row(
children: [
const Text('尺寸', style: TextStyle(color: Colors.black, fontSize: 16)),
const SizedBox(width: 10),
Container(color: const Color(0x19333333), height: 18, width: 1),
const SizedBox(width: 10),
const SingleSelectSize(
sizeList: ['S', 'M', 'L'],
),
],
),
],
),
],
),
),
);
}
}
class SingleSelectColor extends StatelessWidget {
final List<MaterialColor> colorList;
const SingleSelectColor({super.key, required this.colorList});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List<Widget>.generate(
colorList.length,
(int index) {
return Consumer<MainViewModel>(
builder: (context, cartModel, child) =>
Row(
children: [
OutlinedButton(
onPressed: () {
context.read<MainViewModel>().onColorSelected(index);
},
style: ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
minimumSize: MaterialStateProperty.all(const Size(40, 40)),
padding: MaterialStateProperty.all<EdgeInsets>(const EdgeInsets.all(0)),
side: MaterialStateProperty.all(BorderSide(color: getColor(index, cartModel.selectedColorIndex), width: 1.0, style: BorderStyle.solid)),
shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
),
child: Container(color: colorList[index], height: 18, width: 18)),
],
),
);
},
),
);
}
Color getColor(int index, int selectedColorIndex) {
return selectedColorIndex == index ? Colors.black : Colors.transparent;
}
}
class SingleSelectSize extends StatelessWidget {
final List<String> sizeList;
final grey = const Color.fromARGB(255, 87, 99, 108);
final white = Colors.white;
const SingleSelectSize({super.key, required this.sizeList});
@override
Widget build(BuildContext context) {
Color getBackgroundColor(int index, int selectedSizeIndex) {
return selectedSizeIndex == index ? white : grey;
}
Color getTextColor(int index, int selectedSizeIndex) {
return selectedSizeIndex == index ? grey : white;
}
return Consumer<MainViewModel>(
builder: (context, cartModel, child) => Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List<Widget>.generate(
sizeList.length,
(int index) {
return Row(
children: [
ElevatedButton(
onPressed: () {
context.read<MainViewModel>().onSizeSelected(index);
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.only(top: 3, bottom: 3, left: 10, right: 10),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: getBackgroundColor(index, cartModel.selectedSizeIndex),
),
child: Text(sizeList[index], style: TextStyle(color: getTextColor(index, cartModel.selectedSizeIndex), fontSize: 12)),
),
const SizedBox(width: 10),
],
);
},
),
)
);
}
}
```
```
import 'package:flutter/cupertino.dart';
class MainViewModel extends ChangeNotifier {
int selectedColorIndex = -1;
int selectedSizeIndex = -1;
void onColorSelected(int index) {
selectedColorIndex = index;
selectedSizeIndex = -1;
notifyListeners();
}
void onSizeSelected(int index) {
selectedSizeIndex = index;
notifyListeners();
}
}
```