---
# System prepended metadata

title: InheritedWidget vs Provider

---

# 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();
  }
}
```