I am using ScopedModel and ScopedModelDescendent in my flutter app. The MaterialApp is wrapper within a ScopedModelDescendent widget as shown in the code below.
void main() => runApp(new ScopeModelWrapper());
class ScopeModelWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<AppModel>(model: AppModel(), child: MyApp());
}
}
class AppModel extends Model {
// Some code specific to model, actually a lot of code.
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<AppModel>(builder: (context, child, model) {
return MaterialApp(
title: 'Flutter Firebase',
localizationsDelegates: [
AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
Locale("nb", ""),
Locale("en", ""),
],
locale: model.appLocal,
navigatorObservers: <NavigatorObserver>[routeObserver],
theme: new ThemeData(
primarySwatch: Colors.blue,
primaryColor: Colors.blueAccent,
),
home: SelectDenominationPage(),
);
});
}
}
The issue occurs when I navigate to HomePage->Settings (from the SelectDenominationPage). The Settings page (or widget) contains some widgets wrapped inside ScopedModelDescendent widget and that is what causes the error.
I do not understand why it cannot find a ScopedModel because it is right there at the root of the tree.
Any help is highly appreciated.
Update: I moved the AppModel class to its own file and then I changed all my imports from 'appmodel.dart' to 'package:<projectname>/appmodel.dart' and it started working. I still do not understand this completely. If we are referencing to the same file, why is it being treated at a different model then?
Related
I am trying to build a gridview nested in a listview with flutter, based on the attached minimal viable example, while trying to leverage the inner gridviews cacheExtent feature, which shall prevent items to be built all at once. In the full application i do kind of heavy stuff, so i want as few items to be loaded at the same time as possible, but unfortunately it looks like cacheExtent is ignored for the inner list, when nesting multiple list. For the surrounding list cacheExtent works as expected.
Does anyone have a solution for this and can explain me, while it won't work for the inner list? I am not only looking for a copy&paste solution, i am really trying to understand whats going on in flutter here, since i guess it is caused by any fundamental layouting policy which i don't know yet.
Environment:
[✓] Flutter (Channel stable, 2.10.0, on macOS 12.2.1 21D62 darwin-arm, locale en-DE)
[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
Example:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:developer';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
// works as expected, second instance of InnerList will not be loaded on startup
cacheExtent: 1,
shrinkWrap: true,
itemCount: 2,
itemBuilder: (context, index) {
return InnerList(index);
},
),
appBar: AppBar(
title: const Text('Home'),
),
drawer: const Drawer(
child: Text("Foobar"),
),
);
}
}
class ItemWidget extends StatelessWidget {
int index;
ItemWidget(this.index);
#override
Widget build(BuildContext context) {
// indicates all widgets are getting build at once, ignoring cacheExtent
log("building widget " + index.toString());
// TODO: implement build
return SizedBox(
height: 120,
width: 120,
child: Center(child: Text(index.toString())),
);
}
}
class InnerList extends StatelessWidget {
int index;
InnerList(this.index);
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Foobar " + index.toString()),
GridView.builder(
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120.0,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
),
itemCount: 100,
primary: false,
// this is ignored, all items of type ItemWidget will be loaded at once
cacheExtent: 1,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ItemWidget(index);
})
],
);
}
}
// Update
The accepted answer pointed me into the right direction, leading my search to a video explaining why it won't work - the simple answer is: shrinkWrap forces lists to evaluate all childs, to determine their height. It is even shown on a very similar example, using shrinkWrap and physics properties of the list. The solution with Slivers now looks similar like follows, even if i am still kind of skeptic constructing the outer list used by CostumScrollView in a loop.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:developer';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> innerLists = [];
#override
void initState() {
super.initState();
// construct lists
for(var i=0; i<10; i++) {
innerLists.add(SliverToBoxAdapter(
child: Text("Foobar " + i.toString())
));
innerLists.add(InnerList(i));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(slivers: innerLists),
appBar: AppBar(
title: const Text('Home'),
),
drawer: const Drawer(
child: Text("Foobar"),
)
);
}
}
class ItemWidget extends StatelessWidget {
int index;
ItemWidget(this.index);
#override
Widget build(BuildContext context) {
// TODO: implement build
return SizedBox(
height: 120,
width: 120,
child: Center(child: Text(index.toString())),
);
}
}
class InnerList extends StatelessWidget {
int index;
InnerList(this.index);
#override
Widget build(BuildContext context) {
return
SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120.0,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ItemWidget(index);
},
childCount: 100
)
);
}
}
shrinkWrap makes GridView/ListView decides height itself.
Without shrinkWrap, ListView expands to main axis infinitely. So its height is constrained by parent box constraint, and fills available space. In that case render space is definite. ListView only build childs on the visible space.
With shrinkWrap, ListView try to shrink, as short as possible unless it can contain all childs, so its height is sum of child's height (roughly). In this case ListView doesn't see parent constraint so ListView doesn't know how many childs it should build, so it builds all elements.
Now let's see your example. With replacing log to print, we get the following.
building widget 0
building widget 1
...
building widget 99
building widget 0
building widget 1
...
building widget 99
Although I can't see the second InnerList, ListView built the second child. Now with commenting out ListView.builder's shrinkWrap
building widget 0
building widget 1
...
building widget 99
If you scroll down, log will be same with the first.
GridView is same. But you can't comment out GridView's shrinkWrap due to layout error. It is because ListView gives infinite height to its children (GridView). If GridView expands, its height will be infinity.
Of course using SizedBox will solve this partially, but maybe it is not what you want.
If you really want on demand load here, you may use sliver.
Add: cacheExtent doesn't mean item count. It is pixel count.
I am new to flutter, BUILT an app showing graphs in flutter. When i run the code a red screen is appearing showing the above error
No MediaQuery widget ancestor found. Scaffold widgets require a MediaQuery widget ancestor.
I have cleared all the errors except this one.
Full link of the code
Check you main.dart, and in the MyApp (Or whatever you named it) class, you should wrap it with MaterialApp. It should fix the problem.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Graphn',
home: GraphScreen(),
);
}
}
Wrap your scaffold to MatertialApp or you can make a class and in that class call GraphScreen like this
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: GraphScreen(),
);
}
}
As in the titel, I have a problem with ListView and I hope you can help me out.
I am using a basic ListView to build "Card Widgets" (with their own state). The ListView uses a List of Ids, which are used to build those "Card Widgets"
The problem:
Any time I remove a card from the list by deleting an Id the ListView always removes the top most Child Widget. My backend deletes the right things, becouse after I restart the app so that the page gets populated anew, the deleted card is actually deleted and the one the removed by the ListView is visible again. It seems like ListView does not redraw it's children. Any Idea what is going on?
I created basic DartPad code to illustrate the problem
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
List<String> dd = new List<String>();
#override
void initState() {
super.initState();
dd.add('A');
dd.add('B');
dd.add('C');
dd.add('D');
}
void _incrementCounter() {
setState(() {
_counter++;
dd.insert(1, 'Q');
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title + _counter.toString()),
),
body: ListView.builder(
addAutomaticKeepAlives: false,
itemBuilder: (context, index) {
print('calling: $index :' + _counter.toString() + ' -> ' + dd[index] );
return new CRD(title: dd[index]);
},
itemCount: dd.length
),
/*
ListView(
children: dd.map((str) {
return CRD(title: str);
}).toList()
),
*/
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class CRD extends StatefulWidget {
CRD({Key key, this.title}) : super(key: key);
final String title;
#override
_CRD createState() => _CRD();
}
class _CRD extends State<CRD> {
String _val;
#override
void initState() {
super.initState();
_val = widget.title + ' ' + widget.title;
}
#override
Widget build(BuildContext context) {
return Text(_val);
}
}
So after clicking once on the Add button the list content is [A,Q,B,C,D] but the app displays [A,B,C,D,D]. Whats going on here? Am i missing something?
Your CRD widget is a StatefulWidget and the state will be reused when rebuilding since the type of the widget is the same an you did not give it a key.
To solve your issue there are a few possibilities:
Add a key to all the items in the list
Implement the didUpdateWidget method in the state of your widget
Use a statelesswidget and do the string concatination in the build method
I have an issue with flutter. I have managed to implement a basic navigation system that keeps state when you do either of the following:
switch between tabs
press the android home button and re-open the app (either by clicking on the app again or using the list of active app button (the little square at the bottom))
But if I press the back button - going back to the android homescreen I completely lose state. I have re-implemented some code to randomly generate a number and display it on the app - this way I know if I'm getting the same widget or a new one has been built.
Why do I need this? (if you're interested)
I'm creating an audio app and when I click play song, it plays. but when I click back to the home screen and let it play in the background -> then open the app again, I can play it again and have it playing twice!
Main:
import 'package:flutter/material.dart';
import 'BottomNavigationBarController.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Login',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BottomNavigationBarController(),
);
}
}
Bottom navigation tab (BottomNavigationBarController):
import 'package:flutter/material.dart';
import 'PlaceholderWidget.dart';
class BottomNavigationBarController extends StatefulWidget {
BottomNavigationBarController({Key key}) : super(key: key);
#override
_BottomNavigationBarController createState() => _BottomNavigationBarController();
}
class _BottomNavigationBarController extends State<BottomNavigationBarController>{
int _selectedPage = 0;
List<Widget> pageList = List<Widget>();
#override
void initState() {
pageList.add(PlaceholderWidget());
pageList.add(PlaceholderWidget());
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _selectedPage,
children: pageList,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.phone_android),
title: Text('First Page'),
),
BottomNavigationBarItem(
icon: Icon(Icons.phone_android),
title: Text('Second Page'),
),
],
currentIndex: _selectedPage,
selectedItemColor: Colors.blue,
onTap: _onItemTapped,
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void _onItemTapped(int index) {
setState(() {
_selectedPage = index;
});
}
}
Random number widget (PlaceholderWidget):
import 'dart:math';
import 'package:flutter/material.dart';
class PlaceholderWidget extends StatefulWidget {
PlaceholderWidget({Key key, this.color}) : super(key: key);
final Color color;
#override
_PlaceholderWidget createState() => _PlaceholderWidget();
}
class _PlaceholderWidget extends State<PlaceholderWidget> with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
return Container(
color: widget.color,
child: Text(random_num().toString()),
);
}
int random_num(){
Random random = new Random();
int randomNumber = random.nextInt(100);
return randomNumber;
}
}
Any help will be appreciated :)
I think Navigator pop deletes the widget not entirely sure. But If you want to save the current state just use navigator push don't pop. Also use named routes this will help you greatly.
Use Provider to pass state and keep a global store.
If your app will need to scale, now is a good time to start with MobX/BLoC/Redux/InheritedWidget.. etc.
I was writing a code for my flutter application. In that I needed to get size of screen of mobile so I used Media Query but error occurred saying "MediaQuery was called with no context" but I was passing the context.
Here is the code
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
return MaterialApp(
title: 'Text',
home: Scaffold(
body: Container(
height: height,
width: width,
),
),
);
}
}
How can I solve this error. Please help. Thanks in advance.
You can't do the MediaQuery.of(context) call in the MaterialApp create Methode, because the app does not know anything about the Media. The error message is
MediaQuery.of() call with a context that does not contain a MediaQuery
You can use this call after the app is running but not in the start up sequence.
Maybe you create an extra widget underneath the MaterialApp. Then it would work.
It should be working by the way anyways run flutter clean from cmd or your console and then try running the app.
If it is not working then directly give the height and the width of the scaffold,MediaQuery.of(context).size.height, and MediaQuery.of(context).size.width, respectively and some modifications like this:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('text'),
),
body: Container(
color:Colors.red, //Only to se if the container is being displayed.
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
),
);
}
}
This would definitely work.
if you still face any difficulties then you can ask in the comments.