简体   繁体   English

Flutter - MultiProvider 如何与相同类型的提供者一起工作?

[英]Flutter - How does MultiProvider work with providers of the same type?

For example, I am trying to obtain data emitted for multiple streams at once, but 2 or more of these streams emit data of the same type, lets say a string.例如,我试图一次获取为多个流发出的数据,但是这些流中的 2 个或更多发出相同类型的数据,比如说一个字符串。

My question is, is it possible to use MultiProvider and use multiple StreamProvider (or any provider, but I am interested in this case) of the same type while still being able to access the data emitted by each one of them?我的问题是,是否可以使用MultiProvider并使用相同类型的多个StreamProvider (或任何提供者,但我对这种情况感兴趣),同时仍然能够访问每个提供者发出的数据?

A solution for this is using a StreamBuilder when using common data types but I really like what the MultiProvider offers in terms of cleaner code.一个解决方案是在使用常见数据类型时使用StreamBuilder ,但我真的很喜欢MultiProvider在更清晰的代码方面提供的功能。

Example:例子:

class MyScreen extends StatelessWidget {
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        StreamProvider<String>(stream: Observable.just("stream1")),
        StreamProvider<String>(stream: Observable.just("stream2")),
        StreamProvider<String>(stream: Observable.just("stream3"))
      ],
      child: Builder(
        builder: (BuildContext context) {
          AsyncSnapshot<String> snapshot =
              Provider.of<AsyncSnapshot<String>>(context);
          String data = snapshot.data;
          return Text(data); 
        },
      ),
    );
  }
}

MultiProvider or not doesn't change anything. MultiProvider与否不会改变任何东西。 If two providers share the same type, the deepest one overrides the value.如果两个提供程序共享相同的类型,则最深的一个会覆盖该值。

It's not possible to obtain the value from a provider that is not the closest ancestor for a given type.不可能从不是给定类型最接近祖先的提供者那里获取值。

If you need to access all of these values independently, each should have a unique type.如果您需要独立访问所有这些值,则每个值都应具有唯一的类型。

For example, instead of:例如,而不是:

Provider<int>(
  value: 42,
  child: Provider<int>(
    value: 84,
    child: <something>
  ),
)

You can do:你可以做:

class Root {
  Root(this.value);

  final int value;
}

class Leaf {
  Leaf(this.value);

  final int value;
}


Provider<Root>(
  value: Root(42),
  child: Provider<Leaf>(
    value: Leaf(84),
    child: <something>
  ),
)

This allows to obtain each value independently using:这允许使用以下方法独立获取每个值:

Provider.of<Root>(context)
Provider.of<Leaf>(context);

UPDATE: This solution I recently found seems to be cleaner and working better.更新:我最近发现的这个解决方案似乎更干净,工作得更好。 The solution below is another way but requires more coding.下面的解决方案是另一种方式,但需要更多的编码。


I was looking for a similar solution and couldn't find anything so I implemented my own with the MultiProvider, StreamGroup, and a ChangeNotifier.我一直在寻找类似的解决方案,但找不到任何解决方案,因此我使用 MultiProvider、StreamGroup 和 ChangeNotifier 实现了自己的解决方案。 I use the StreamGroup to hold all the streams I need to keep track of by adding and removing streams.我使用StreamGroup来保存我需要通过添加和删除流来跟踪的所有流。 I didn't want to use a bunch of extra libraries and/or plugins.我不想使用一堆额外的库和/或插件。

In the ChangeNotifierProxyProvider , it runs the update function whenever the Family stream gets an update from StreamProvider<Family> above it.ChangeNotifierProxyProvider ,只要Family流从其上方的StreamProvider<Family>获得更新,它就会运行update函数。

// main.dart
@override
Widget build(BuildContext context) {
  return MultiProvider(
    providers: [
      StreamProvider<Family>(
        initialData: Family(),
        create: (context) => FirebaseFireStoreService().streamFamilyInfo(),
      ),
      ChangeNotifierProxyProvider<Family, FamilyStore>(
        create: (context) => FamilyStore(),
        update: (context, family, previousFamilyStore) {
          // Manually calling the function to update the 
          // FamilyStore store with the new Family
          previousFamilyStore!.updateFamily(family);
          return previousFamilyStore;
        },
      )
    ],
    builder: (context, child) => MaterialApp(),
  );
}

The Family just holds an array of AdultProfile uid s so I can keep track of the adults in a family. Family只保存了一个AdultProfile uid数组,所以我可以跟踪一个家庭中的成年人。 It's basically just a stream receiver.它基本上只是一个流接收器。

// family.dart
class Family {
  List<String> adults;

  Family({
    this.adults = const [],
  });

  factory Family.fromMap(Map<String, dynamic>? data) {
    if (data == null) {
      return Family(adults: []);
    }

    return Family(
      adults: [...data['adults']],
    );
  }
}

In my firestore class , I have the necessary functions that return a Stream for the class I need.在我的 firestore class ,我有必要的函数可以为我需要的class返回一个Stream I only pasted the function for the Family , but same code for AdultProfile with minor path changes.我只粘贴了Family的函数,但为AdultProfile粘贴了相同的代码,但路径变化很小。

// firebase_firestore.dart
Stream<Family> streamFamilyInfo() {
  try {
    return familyInfo(FirebaseAuthService().currentUser!.uid).snapshots().map(
      (snapshot) {
        return Family.fromMap(snapshot.data() as Map<String, dynamic>);
      },
    );
  } catch (e) {
    FirebaseAuthService().signOut();
    rethrow;
  }
}

This is where most of the work happens:这是大部分工作发生的地方:

// family_store.dart
class FamilyStore extends ChangeNotifier {
  List<AdultProfile>? adults = [];
  StreamGroup? streamGroup = StreamGroup();

  FamilyStore() {
    // Handle the stream as they come in
    streamGroup!.stream.listen((event) {
      _handleStream(event);
    });
  }

  void handleStream(streamEvent) {
    // Deal with the stream as they come in 
    // for the different instances of the class
    // you may have in the data structure
    if (streamEvent is AdultProfile) {
      int index = adults!.indexWhere((element) => element.uid == streamEvent.uid);
      if (index >= 0) {
        adults![index] = streamEvent;
      } else {
        adults!.add(streamEvent);
      }
      notifyListeners();
  }

  // This is the function called from the
  // ChangeNotifierProxyProvider in main.dart
  void updateFamily(Family newFamily) {
    _updateAdults(newFamily.adults);
  }

  void _updateAdults(List<String> newAdults) async {
    if (newAdults.isEmpty) return;

    // Generate list of comparisons so you can add/remove
    // streams from StreamGroup and the array of classes
    Map<String, List<String>> updateLists =
        _createAddRemoveLists(adults!.map((profile) => profile.uid).toList(), newAdults);

    for (String uid in updateLists['add']!) {
      // Add the stream for the instance of the 
      // AdultProfile to the StreamGroup
      (streamGroup!.add(
        FirebaseFireStoreService().streamAdultProfile(uid),
      ));
    }

    for (String uid in updateLists['remove']!) {
      // Remove the stream for the instance of the 
      // AdultProfile from the StreamGroup
      streamGroup!.remove(
        FirebaseFireStoreService().streamAdultProfile(uid),
      );
      // Also remove it from the array
      adults!.removeWhere((element) => element.uid == uid);
      notifyListeners();
    }
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM