Giter VIP home page Giter VIP logo

Comments (3)

brianegan avatar brianegan commented on June 12, 2024 4

Hey, happy to help. Do you happen to have any code you could share?

One common mistake I've seen with ScopedModel / InheritedWidget usage is that you might be accidentally referencing the context which is a parent to the ScopedModel, rather than a context that includes the ScopedModel.

For example, trying to show a Snackbar in this way won't quite work because of the same problem:

class MyHomePage extends StatelessWidget {
  final String title;

  const MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Broken Button'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // This will fail, because we're referencing a context that doesn't include the Scaffold
          Scaffold.of(context).showSnackBar(SnackBar(content: Text('Hi')));
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

The fix? Wrap the FloatingActionButton in a new Widget, which will create a new context that includes the Scaffold!

class MyHomePage extends StatelessWidget {
  final String title;

  const MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Broken Button'),
      ),
      floatingActionButton: new MyButton(),
    );
  }
}

class MyButton extends StatelessWidget {
  const MyButton({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        Scaffold.of(context).showSnackBar(SnackBar(content: Text('Hi')));
      },
      child: Icon(Icons.add),
    );
  }
}

Not sure if this was your problem, just one I commonly see! If you've got another issue going on, please let me know and it'd be great to have some code so I could take a look :)

I'm definitely open to the possibility there's a bug somewhere!

from scoped_model.

brianegan avatar brianegan commented on June 12, 2024 1

Hey there -- sorry about such a long delay :/ Was super busy in July then dropped the ball on this one.

Hrm, I'm not 100% sure what could be going wrong. As a test, I made the following example and it seems to work fine.

One other common problem: You need to ensure you're importing all files with their full package path, otherwise Dart can resolve the class incorrectly.

For example, in main.dart, rather than doing an import 'app_config.dart'; use import 'package:my_app/app_config.dart';. You can see other's who've run into this bug here: #15 and the Issue for the Dart team here: dart-lang/sdk#33076

import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';

class AppConfig extends InheritedWidget {
  final String apiURL;

  AppConfig({
    @required this.apiURL,
    @required Widget child,
  }) : super(child: child);

  static AppConfig of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(AppConfig);
  }

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => false;
}

void main() {
  runApp(AppConfig(
    apiURL: 'hello',
    child: MyApp(
      model: CounterModel(),
    ),
  ));
}

class MyApp extends StatelessWidget {
  final CounterModel model;

  const MyApp({Key key, @required this.model}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // At the top level of our app, we'll, create a ScopedModel Widget. This
    // will provide the CounterModel to all children in the app that request it
    // using a ScopedModelDescendant.
    return ScopedModel<CounterModel>(
      model: model,
      child: MaterialApp(
        title: 'Scoped Model Demo',
        home: CounterHome('Scoped Model Demo'),
      ),
    );
  }
}

// Start by creating a class that has a counter and a method to increment it.
//
// Note: It must extend from Model.
class CounterModel extends Model {
  int _counter = 0;

  int get counter => _counter;

  void increment() {
    // First, increment the counter
    _counter++;

    // Then notify all the listeners.
    notifyListeners();
  }
}

class CounterHome extends StatelessWidget {
  final String title;

  CounterHome(this.title);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            // Create a ScopedModelDescendant. This widget will get the
            // CounterModel from the nearest parent ScopedModel<CounterModel>.
            // It will hand that CounterModel to our builder method, and
            // rebuild any time the CounterModel changes (i.e. after we
            // `notifyListeners` in the Model).
            ScopedModelDescendant<CounterModel>(
              builder: (context, child, model) {
                return Text(
                  '${model.counter}, Api URL: ${AppConfig.of(context).apiURL}',
                );
              },
            ),
          ],
        ),
      ),
      // Use the ScopedModelDescendant again in order to use the increment
      // method from the CounterModel
      floatingActionButton: ScopedModelDescendant<CounterModel>(
        builder: (context, child, model) {
          return FloatingActionButton(
            onPressed: model.increment,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          );
        },
      ),
    );
  }
}

from scoped_model.

 avatar commented on June 12, 2024

Brian,

Thank you for the quick response. I've tried again with this wrapping of my widgets to access the proper context but I've still come up short.

I will detail my code below...


I have an AppConfig class that will contain an API URL that will differ on each build. From my knowledge, creating this inherited widget subclass is supposed to allow me to reach in and get the .apiURL string value after using the static function; AppConfig.of(context).apiURL

class AppConfig extends InheritedWidget {
final String apiURL;

AppConfig({
@required this.apiURL,
@required Widget child,
}) : super(child: child);

static AppConfig of(BuildContext context) {
return context.inheritFromWidgetOfExactType(AppConfig);
}

@OverRide
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}


Here is my main_dev.dart file that I run when I test the app.

void main() {
var configuredApp = AppConfig(
apiURL: 'https://dev-api.example.com/',
buildIdentifier: BuildIdentifier.DEV,
buildName: 'Development',
child: MyApp(),
);

runApp(configuredApp);
}


Here is my lib/main.dart file with the routing AND the Scoped Model

class MyApp extends StatefulWidget {
@OverRide
State createState() {
return _MyAppState();
}
}

class _MyAppState extends State {
final MainModel _model = MainModel();

@OverRide
Widget build(BuildContext context) {
return ScopedModel(
child: MaterialApp(
routes: {
'/': (BuildContext context) => MainScreen(_model),
},
theme: getThemeData(context),
title: 'My App',
),
model: _model,
);
}
}


This is where I'm having my context give me questionable behavior, where I'm unable to use the context to then use my AppConfig's static function on to fetch my build's API URL.

class MainScreen extends StatefulWidget {
final MainModel model;

MainScreen(this.model);

@OverRide
State createState() {
return _MainScreenState();
}
}

1. This way about it doesn't work...

class _MainScreenState extends State {
@OverRide
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppConfig.of(context).apiURL), // The getter 'apiURL' was called on null, and the whole screen is a sea of red.
),
body: _buildGridView(context),
);
}
}

2. This new way wrapping the context in a new class widget doesn't work either.

class _MainScreenState extends State {
@OverRide
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: BrowseTitle(),
),
body: _buildGridView(context),
);
}
}

class BrowseTitle extends StatelessWidget {
@OverRide
Widget build(BuildContext context) {
var appConfig = AppConfig.of(context);
return Text(appConfig.apiURL); // The getter 'apiURL' was called on null, but only the app bar title widget comes out as a red box
}
}


Thanks for taking a look at this, Brian.

from scoped_model.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.