The element type 'String' can't be assigned to the list type 'Widget' - flutter

I'm new to flutter, would like to do a List to display the image and title in Grid View Builder however I met an error. I also have to call out the list however not sure what's the issue for the error which I'm don't know how to fix. Error coming out on the code with "appItem.items[i].appImage". Much appreciate your help!
List<AppProfile> get items {
return [..._items];
}
My array(still have quite a lot however only take these two for example)
List<AppProfile> _items = [
AppProfile(
appID: "",
title: "Whatsapp",
appImage: "assets/images/Materials-03.png",
appLink: "",
),
AppProfile(
appID: "",
title: "Wechat",
appImage: "assets/images/Materials-04.png",
appLink: "",
),
]
in my builder/ display file
Widget build(BuildContext context) {
final appItem = Provider.of<AppLogic>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: const Text("Your Social Media"),
),
body:
Container(
child: GridView.builder(
padding: EdgeInsets.all(30),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 2 / 2,
crossAxisSpacing: 30, //spacing between column
mainAxisSpacing: 20,
),
itemCount: appItem.items.length,
itemBuilder: (ctx, i) {
return GestureDetector(
onTap: () {},
child: Column(
children: <Widget> [
**appItem.items[i].appImage**,
]
),
);
},
),
),
);
Error occurs
Error: Could not find the correct Provider above this SocialProfileScreen Widget
This happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
You added a new provider in your main.dart and performed a hot-reload.
To fix, perform a hot-restart.
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that SocialProfileScreen is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
However in my home page have floating button
Widget build(BuildContext context) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
// "Navigator.push" returns a Future and "onPressed" accepts void
//so cannot directly use "Navigator" but if using () =>
//simply means that executing "Navigator..."
onPressed: () =>
Navigator.of(context).pushNamed(SocialProfileScreen.routeName),
),

The error occurs because your appItem.items[i].appImage returns a String instead of a Widget. In this case, you need to wrap it with a Widget that displays an image.
However, you can't use a String, double, bool or other variables into a list which is the Widgets list.
So you should add an Image.assets() widget or another one (which displays images), and give your image path.
Like:
Image.asset(appItem.items[i].appImage);

Related

Flutter Looking up a deactivated widget's ancestor on dialog pop

I'm having an issue trying to pop a dialog that contains a circle loader. I actually pop fine once my data is loaded, but in debug mode it's showing an exception that I can't figure out how to fix.
I have a stateful screen that on init I use the following code:
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
showLoading();
});
The method showLoading is as follows:
void showLoading() {
//let's show the loading bar
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
dialogContext = context;
return AppLoader();
},
);
}
Where AppLoader simply returns:
class AppLoader extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
backgroundColor: Colors.transparent,
body: Center(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
SizedBox(
child: new CircularProgressIndicator(),
height: 80.0,
width: 80.0,
),
],
),
),
);
}
}
dialogContent is defined in the initial of the class as:
late BuildContext dialogcontext;
The main bulk of my code looks like this:
#override
Widget build(BuildContext context) {
return Container(
color: ColorConstant.gray100,
child: Scaffold(
backgroundColor: ColorConstant.gray100,
body: Stack(
children: <Widget>[
getMainListViewUI(),
SizedBox(
height: MediaQuery.of(context).padding.bottom,
)
],
),
),
);
}
Widget getMainListViewUI() {
return FutureBuilder<bool>(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return ListView.builder(
itemCount: listViews.length,
scrollDirection: Axis.vertical,
itemBuilder: (BuildContext context, int index) {
return listViews[index];
},
);
},
);
}
Basically, the issue that I have is that when I finish getting the data from (getData()), I use:
Navigator.pop(dialogContext);
This works great: it removes the circle loader and I can see the screen behind it, no issues, no errors. However, if I run in debug mode, when I do a hotsync, it always shows me the error:
Looking up a deactivated widget's ancestor on dialog pop
I understand that this is because of the Navigator.pop that I am doing, but I don't get it. I've defined the dialogContext, which is what I am passing to the showDialog, and that's what I am popping. I've also tried setting a scheduled navigator, but again, same issue.
Any advice please?
You can check if widget is mounted or not. This problem is likely to occur when you pass a context and have async function. Can you add this before navigation and see if problem is solved
if(!mounted) return;
Problem solved. The issue is the my getData method was being called multiple times, the first time it pops the loading widget fine, after that it would throw the error which is correct since the loading widget no longer exists.

use only one page with DefaultTabController of 3 TabBars

I am using DefaultTabController for 3 TabBars. But i need to use only one page to be shown fetching the data from database all three TabBars are the same, But only the fetched data is different according to the index of its tap. How could i do so without create 3 copies of the same object WidgetOfTape?
the code is:
DefaultTabController(
length: 3,
child: Scaffold(
body: const TabBarView(
children: [
WidgetOfTape(),
WidgetOfTape(),
WidgetOfTape(),
],
),
WidgetOfTape code is:
class WidgetOfTape extends HookWidget {
const WidgetOfTape ();
#override
Widget build(BuildContext context) {
return BlocSelector<ScheduledPossibleDayCubit, ScheduledPossibleDayState,
int>(selector: (state) {
return state.indexOfTap;
}, builder: (context, state) {
return BlocProvider<DisplayNoteInsidePagesCubit>(
lazy: false,
create: (context) => getIt<DisplayNoteInsidePagesCubit>()
..countDoneNoteOutOfAllNotes()
..retrieveData(indexOfTap: state),
child: Column(children: [
Expanded(child: BlocBuilder<DisplayNoteInsidePagesCubit,
DisplayNoteInsidePagesState>(
builder: (context, state) {
return ListView.builder(
shrinkWrap: true,
itemCount: state.notes.length,
itemBuilder: (BuildContext context, int index) {
return Row(
children: [
Expanded(
child: Text(
state.notes[index]['content'],
textAlign: TextAlign.justify,
style: Theme.of(context).textTheme.bodyText2,
),
),
],
);
});
},
))
]));
});
}
}
Yes, you can do that, by sending a new parameter to your WidgetOfTape( ). The new variable you send as a parameter is defined by you. It could be based on the fetched data for example:
DefaultTabController(
length: 3,
child: Scaffold(
body: const TabBarView(
children: [
WidgetOfTape(newParameter1), //*** This line changed ***
WidgetOfTape(newParameter2), //*** This line changed ***
WidgetOfTape(newParameter3), //*** This line changed ***
],
),
Dont forget to add that parameter to your actual class WidgetOfTape( ).
If you dont know how to pass parameters, check here:
In Flutter, how do I pass data into a Stateless Widget?

I can't refresh the screen

I use the get package, I have a bottom navigation that is updated with Obx. There is also a search for elements, at the top level of the pages everything is updated well, but when I do push, the current page is not updated, only when you call hot reload. There are suspicions that the nested page is not updated due to the fact that it goes beyond the BottomNavigation, and there is no Obx widget.
My Page Navigation controller:
Widget build(BuildContext context) {
SizeConfig().init(context);
final BottomPageController landingPageController =
Get.put(BottomPageController(), permanent: false);
return SafeArea(
child: Scaffold(
bottomNavigationBar:
BottomNavigationMenu(landingPageController: landingPageController),
body: Obx(
() => IndexedStack(
index: landingPageController.tabIndex.value,
children: [
ProductPage(),
MapPage(),
ClientPage(),
NotePage(),
],
),
),
),
);
}
My page where you need to update the ListView, depending on the entered value in the search bar:
class Product extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetX<ProductController>(
init: ProductController(),
builder: (controller) {
return FutureBuilder<List<ProductsCombined>>(
future: controller.filterProduct.value,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Expanded(
child: Container(
child: ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) =>
ProductCards(
product: snapshot.data[index],
),
),
),
);
} else {
return Center(child: CircularProgressIndicator());
}
},
);
},
);
}
}
There is also a nested page in the Product class, which is accessed in this way:
onTap: () => {
Get.toNamed(StoresScreen.routeName, arguments: companies)}
The Product class is updated immediately, you do not need to click Hot reload to do this, and the ProductScreen class that the transition is made to can no longer be updated, these 2 classes are completely identical. The search bar works, but it filters out the elements only after Hot reload.
If you need some other parts of my code, such as a controller that handles ListView, please write, I will attach it.
EDIT: I add a link to the video, with the screen that is not updated
Obx is a little tricky to work with. I always suggest to use GetX instead.
Try this :
Obx(
() => IndexedStack(
index: Get.find<BottomPageController>().tabIndex.value,
children: [
...
],
),
),
If it didn't work use GetX<BottomPageController> for this situation too. It works for all conditions

Navigator and Provider conflict in flutter

sending data between screen using Provider with Navigator concept make a conflict
error after the run
The following ProviderNotFoundError was thrown building SecondRoute(dirty):
Error: Could not find the correct Provider above this SecondRoute Widget
To fix, please:
Ensure the Provider is an ancestor to this SecondRoute Widget
Provide types to Provider
Provide types to Consumer
Provide types to Provider.of()
Always use package imports. Ex: `import 'package:my_app/my_code.dart';
Ensure the correct context is being used.
https://www.ideone.com/xHXK5m
you can pass data like this put data in class
RaisedButton(
child: Text(‘Send data to the second page’),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(
data: data,
)),
);
},
),
and recieve data like this
class SecondPage extends StatelessWidget {
final Data data;
SecondPage({this.data});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(‘Constructor — second page’),
),
body: Container(
padding: EdgeInsets.all(12.0),
alignment: Alignment.center,
child: Column(
children: <Widget>[
Container(
height: 54.0,
padding: EdgeInsets.all(12.0),
child: Text(‘Data passed to this page:’,
style: TextStyle(fontWeight: FontWeight.w700))),
Text(‘Text: ${data.text}’),
Text(‘Counter: ${data.counter}’),
Text(‘Date: ${data.dateTime}’),
],
),
),
);
}
You can try placing whatever Provider value you have above the MaterialApp and thus the navigation or, when you push to a second page, provide the value again. Providers are scoped to widget trees, so this is expected behavior.
As an example, to pass a value to another route, you could do something like
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
final foo = 'foo';
Provider<String>.value(
value: foo,
child: NewPage(),
);
}));
},
And then just consume it like regular in the NewPage route.
Try this code:
MaterialApp(
title: 'Navigation Basics',
home: ChangeNotifierProvider(
builder: (context) => Data(),
child: FirstRoute(),
))
Delete ChangeNotifierProvider in FirstRoute

Navigator gets the wrong context

I have a flutter application which shows a camera, then scans a qr-code using git://github.com/arashbi/flutter_qrcode_reader and on the call back, I want to navigate to another screen to show the information I get from some query. The problem is, it seems Navigator get a wrong context, so instead of full page navigation, I see the second page pushed inside my first page as a widget.
The build of first page is as follow :
#override
Widget build(BuildContext context) {
Widget main =
new Scaffold(
appBar: _buildAppBar(),
body: Column(
children: <Widget>[
_eventButton ?? new Text(""),
new Center(
child: new Text("Hi"))
],
),
floatingActionButton: new FloatingActionButton(
onPressed:
() {
new QRCodeReader()
.setAutoFocusIntervalInMs(200)
.setForceAutoFocus(true)
.setTorchEnabled(true)
.setHandlePermissions(true)
.setExecuteAfterPermissionGranted(true)
.scan().then((barcode) => _showTicketPage(barcode));
}
,
child: new Icon(Icons.check),
),
);
if (!_loading) {
return main;
} else {
return new Stack(
children: [main,
new Container(
decoration: new BoxDecoration(
color: Color.fromRGBO(220, 220, 220,
0.3) // Specifies the background color and the opacity
),
child: new Center(child: new CircularProgressIndicator(),))
]
);
}
}
The 'Hi' Widget is for debugging, and the navigate method is
void _showTicketPage(var barcode) {
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => TicketScreen(barcode)));
}
After getting the barcode, the second screen - TicketScreen - is pushed under 'Hi' Widget. I didn't even know this was possible to do.
I tried to get the context of the screen, save it to a field var, but that didn't help either.
How can I fix it? I ran out of ideas.