4

I've successfully built a NewsScreen around the riverpod ChangeNotificationProvider to poll data from a REST api and then create a listview.

My app has multiple pages controlled by bottom tab navigation. In two of the tabs I want to show the NewsScreen.

The both show up great, but the problem is the datasource is the same. Meaning if on the first page I load a second page of news listings from the API, the other tab shows more news items as well.

The two pages are sharing the same data source as I guess is expected.

This is how I've declared my Provider at the top of my NewsScreen.dart page

final NewsProvider = ChangeNotifierProvider<NewsData>((ref) {
  return NewsData();
});

So in that sense its being used 'globally' by both tabbed pages.

Is there any kind of strategy to have these two pages get their data independently? I don't want to recode two versions of this new page using different providers. I'd ideally have the same code that is getting called 2 times (or more) in the tabs depending on how many tabs of news pages the user wants

Here is the general structure of my app in case it helps, but If there's any hints anyone has on how to do this, or anything I can read up on to figure it out its much appreciated!

main.dart

void main() {

  runApp(
    // Adding ProviderScope enables Riverpod for the entire project
    const ProviderScope(child: MyApp()),
  );

}


class MyApp extends StatelessWidget{
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {

    return  MaterialApp(
        title: Constants.appName,
        home:MainScreen()
        );


  }

mainscreen.dart

Standard persistent_bottom_navigation.   https://pub.dev/packages/persistent_bottom_nav_bar

My code can be provided if needed, but it calls NewsScreen() from 2 of the tabs on a bottom nav bar

NewsScreen.dart

final NewsProvider = ChangeNotifierProvider<NewsData>((ref) {
  return NewsData();
});



class NewsScreen extends StatefulWidget {
  NewsScreen({Key? key}) : super(key: key);


  @override
  _NewsScreenState createState() => _NewsScreenState();
}


class _NewsScreenState extends State<NewsScreen> {
  //NewsScreen({Key? key}) : super(key: key);

  int _allowNextPageLoad = 1;
  ScrollController _scrollController = ScrollController();


  @override
  void initState(){
    super.initState();

    /// LOAD THE DATA ONLY ON THE FIRST LOAD
    context.read(NewsProvider).fetchData;


     (... some scroll controller stuff ...)

  @override
  void dispose(){
      _scrollController.dispose();
      super.dispose();
  }


  @override
  Widget build(BuildContext context) {


    return
      Scaffold(
          appBar: AppBar(
              title: Text("News",
          ),
          body: RefreshIndicator(
            onRefresh: () async {
(...)
            },
            child: Center(
              child: Container(
                child: NewsScreenConsumer(),
            ),
          ),
          ),
      );
  }
}

NewsConsumer.dart

class NewsScreenConsumer extends ConsumerWidget {
  NewsScreenConsumer();


  @override
  Widget build(BuildContext context, ScopedReader watch)
  {
    /// PROVIDER SETUP
    final data = watch(NewsProvider);

        return Container ( ... UI using data from news provider ... );
   }
}

EDIT

I've gotten it to work if I move the NewsProvider = ChangeNotifierProvider into the Screen widget as follows:

class _NewsScreenState extends State<NewsScreen> {

  final NewsProvider = ChangeNotifierProvider<NewsData>((ref) {
    return NewsData();
  });
...

But a new problem arises, I have several UI widgets that depend on the information from NewsProvider and are called from within _NewsScreenState to build out the UI (essentially the widget in NewsConsumer.dart). These no longer have access to the no longer global NewsProvider. I was hoping they could be some kind of 'child' and understand NewsProvider.

For now I can physically move the code of all these widgets into my _NewsScreenState widget so that it has access to the provider, but its not very elegant. Is there a way to keep this NewsProvider as a sort of local variable for just a few 'child' widgets that are used to build the UI for NewsScreen?

Mark
  • 3,653
  • 10
  • 30
  • 62
  • "final NewsProvider". First off, your IDE is yelling at you to start that with a lowercase character. Please respect that. And second, if there's only one of those, there's only one set of data. If you want two sets of data, you need to construct a second provider and a second notifier. – Randal Schwartz Jul 06 '21 at 02:12
  • @RandalSchwartz But lets say someone is building a listview with 1000 items, that would pull in details on a new itemscreen. You wouldn't setup 1000 providers right? Isn't there a generalizable way to have these providers compartmentalized to the screen they come with? And yes my IDE hates me :). Im planning on cleaning up all these name oddities I have showing up in my dart analysis once I get this heavy lifting out of the way – Mark Jul 06 '21 at 02:17
  • Or better yet, what if I wanted to pull this NewsScreen as a new screen as a child of my NewsScreen with its own data stream. With this current code its not possible since the NewsProvider is top level item. Is there a way to declare a new provider as part of when the new screen opens – Mark Jul 06 '21 at 02:21
  • Have you looked into families? https://riverpod.dev/docs/concepts/modifiers/family – Alex Hartford Jul 06 '21 at 13:48
  • @AlexHartford Thank you, just read up on it. This might be what I need, but I'm having trouble fully understanding `.family`. Boiling it down, I'm trying to get multiple instances of my `NewsProvider` that are each tied to the current `NewsScreen` but with data that can be accessed from outside widgets (UI widgets). So that I could theoretically open 1000 `NewsScreen` pages each as a child of the last and they would have their own NewsProvider/NewsData. Do you think family is the answer to that? – Mark Jul 06 '21 at 13:56
  • I would definitely consider it an option, I'm sure you could get something working. Maybe try a simple example first to get the hang of it. You can also look at this example: https://github.com/rrousselGit/river_pod/tree/master/examples/marvel – Alex Hartford Jul 06 '21 at 15:15
  • @AlexHartford Thanks so much for the direction, I made something that seems to be working now. Im worried there is still something very wrong with my logic, but on the face of it, it works. Ive added it as an answer below -- what do you think? – Mark Jul 06 '21 at 15:21

1 Answers1

2

Ok Using Alex Hartford's suggestion above I've looked into the .family concept and I have something that seems to be working.

I'm putting this here incase it helps anyone out there but also incase someone notices some glaring flaw in my thinking:

What I've done is switched to a NewsProvider .family by changing my global call to this

final NewsProvider = ChangeNotifierProvider.family<NewsData, int>((ref, id) {
  return NewsData();
});

Then inside my class _NewsScreenState extends State<NewsScreen> { I'll generate a random # that will serve as the ID for this family member we will be using

class _NewsScreenState extends State<NewsScreen> {

  int _randomNumber = Random().nextInt(100);
...

Now in the places I need to utilize my Provider I call it like this

await context.read(NewsProvider(_randomNumber)).fetchData;

It seems to be working just fine now. I guess there's the chance the same random # would be generated twice so I'll make it chose a random # out of 10milllion or maybe come up with some kind of random hash generator. I'm sure there's some flaw in my logic so I'm still all ears to a better approach!

Mark
  • 3,653
  • 10
  • 30
  • 62