Flutter cannot use URL variable inside Image.network widget - flutter

Im trying to get url from cloud firebase and then use it in Image.network but it doesn't work..
When i hardcode the url inside Image.network it works.. the variable did get the url as the data.
I get an error from image.dart - ImageStreamListener throw error.
this is my code:
class _MemoryCardState extends State<MemoryCard> {
Map<String, dynamic> photos = {};
Future getPhoto() async {
photos.clear();
var db = FirebaseFirestore.instance.collection('photos');
await db.doc(widget.id).get().then((DocumentSnapshot snapshot) {
photos = snapshot.data() as Map<String, dynamic>;
});
}
#override
Widget build(BuildContext context) {
var deviceWidth = MediaQuery.of(context).size.width;
var deviceHeight = MediaQuery.of(context).size.height;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Card(
semanticContainer: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
elevation: 10,
color: Theme.of(context).colorScheme.surfaceVariant,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: SizedBox(
width: deviceWidth * 0.8,
height: deviceWidth * 0.35,
child: InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const Memory()));
},
child: Stack(
children: [
FutureBuilder(
future: getPhoto(),
builder: (context, snapshot) {
String url = photos['url'].toString();
return Hero(
tag: 'image',
child: Image.network(
url,
fit: BoxFit.cover,
width: deviceWidth * 0.8,
color: Colors.white.withOpacity(0.5),
colorBlendMode: BlendMode.modulate,
));
}),
],
),
),
),
),
SizedBox(height: deviceHeight * 0.2),
],
);
}
}

The error you are encountering is likely caused by the fact that the url variable is not yet available when the Image.network widget is first rendered. The FutureBuilder widget is used to handle this issue, but it is not being used correctly in your code.
A FutureBuilder widget should be used to rebuild the widget tree when the future completes.
You should move the FutureBuilder outside of the InkWell and Stack widgets.
FutureBuilder(
future: getPhoto(),
builder: (context, snapshot) {
if (snapshot.hasData) {
String url = photos['url'].toString();
return InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const Memory()));
},
child: Stack(
children: [
Hero(
tag: 'image',
child: Image.network(
url,
fit: BoxFit.cover,
width: deviceWidth * 0.8,
color: Colors.white.withOpacity(0.5),
colorBlendMode: BlendMode.modulate,
)),
],
),
);
} else {
return CircularProgressIndicator();
}
},
);
Also, it will be a better practice to check if the snapshot hasData before trying to access the url.
Also, you can use await keyword to wait for the data retrieval to complete, before using the url value in the Image.network.

give the url which you found from firebase.
If you used URI as DataType in firebase then it is the problem.

Related

Flutter alert box is not updating picked image from gallery

I am using an alert box where I am getting the image from gallery of the user, but the updated image is not getting displayed.
When I close the alert box and again open the alert box, then it's getting displayed. I am using provider package to handle the data.
Here is a video of what I am getting now
Here is my code:
child: ChangeNotifierProvider<MyProvider>(
create: (context) => MyProvider(),
child: Consumer<MyProvider>(
builder: (context, provider, child) {
return Column(
children: [
ElevatedButton(
onPressed: () {
showDialog(
barrierDismissible: true,
context: context,
barrierColor: Colors.black.withOpacity(0.5),
builder: (ctx) => AlertDialog(actions: <Widget>[
----> // alert box styling
Expanded(
child: Column(
children: [
CircleAvatar(
backgroundColor:
Colors.transparent,
radius: 175,
child: ClipOval(
child: provider
.image !=
null
? Image.network(
provider.image
.path,
height: 200,
)
: Image.asset(
'assets/profile.webp',
width: 250.0,
height: 250.0,
fit: BoxFit
.cover,
),
)),
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: <Widget>[
ElevatedButton(
onPressed: () async {
var image = await ImagePicker()
.pickImage(
source: ImageSource
.camera);
provider.setImage(
image);
},
child: Text(
'Use camera',
style: t3b,
),
),
},
child: const Text('Click me ')),
],
);
},
),
),
),
);
}
}
class MyProvider extends ChangeNotifier {
var image;
Future setImage(img) async {
image = img;
notifyListeners();
}
I am also facing the same issue in mobile development then I know we have to rebuild the whole dialog and then it will work well
showDialog(
context: context,
builder: (BuildContext context) {
int selectedRadio = 0; // Declare your variable outside the builder
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
return Column( // Then, the content of your dialog.
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(4, (int index) {
return Radio<int>(
value: index,
groupValue: selectedRadio,
onChanged: (int value) {
// Whenever you need, call setState on your variable
setState(() => selectedRadio = value);
},
);
}),
);
},
),
);
},
);
Use a StatefulBuilder in the content section of the AlertDialog. Even the StatefulBuilder docs actually have an example with a dialog.
What it does is provide you with a new context, and setState function to rebuild when needed.
also sharing the reference I used for this: Reference for solving this same

Calling setState doesn't updateshowDialog content

I have a custom popup built, and an image is supposed to change whenever one of my variables is changed. When I call the setState method, the content in my showDialog doesn't change.
What am I doing wrong, or is there a better approach? Trying to change the state so the image can be changed in the showDialog.
Here's my code:
class LocationManagerPage extends StatefulWidget {
const LocationManagerPage({Key? key}) : super(key: key);
#override
State<LocationManagerPage> createState() => _LocationManagerPageState();
}
class _LocationManagerPageState extends State<LocationManagerPage> {
String downloadURL = "";
Future _uploadFile(String path) async {
// Logic that gets the download url to an image...
// When the download url is found, calling setState method
setState(() {
downloadURL = fileUrl;
});
}
showLocationPopup() {
return showDialog(
context: context,
builder: (context) {
return Center(
child: Material(
child: Container(
width: 427,
height: 676,
decoration: BoxDecoration(...),
child: SingleChildScrollView(
child: Column(
children: [
// Popup UI Widgets,
Center(
child: Container(
height: 150,
width: 150,
decoration: BoxDecoration(),
child: ClipRRect(
child: Image.network(
image,
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(20),
),
),
),
SizedBox(
height: 15,
),
Center(
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () async {
String? imageUrl = await urlFromWebImage();
print(imageUrl);
setState(() {
downloadURL = imageUrl!;
});
},
child: Button(
name: imageName,
),
),
),
),
// The rest of the popup UI
],
),
),
),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
.... // Not Important
);
}
}
To update dialog ui you need to use StatefulBuilder widget on showDialog's builder and use StatefulBuilder's setState.
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) => AlertDialog(
you can use the following approach
first initialize download url like this
ValueNotifier<String> downloadUrl = ValueNotifier("");
ValueListenableBuilder(
valueListenable: downloadUrl,
builder: (context, value, Widget? c) {
return Container(
height: 150,
width: 150,
decoration: BoxDecoration(),
child: ClipRRect(
child: Image.network(
downloadUrl.value, // here put download url it will auto update
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(20),
));
});
and without using setstate put value in download url it will auto update ui
downloadUrl.value = "" //your image url
or you can use StateFulBuilder
setstate rebuild your whole widget but upper approach only build image widget

How to update the ui when my list gets filled with data GetX Flutter

Im trying to show a listView.builder inside a AlertDialog, and Im filling the its list by calling a function everytime the button to open the AlertDialog is pressed but the problem is that the ui doesn’t update when the list is filled with the data, I'm using getX and I'm very new to it, can someone show me what I'm doing wrong?
I'm using the GetX builder:
GetX<DashboardController>(
init: Get.put<DashboardController>(DashboardController()),
builder: (DashboardController dashboardController) {
return GridView.builder(
My Get.dialog function:
return GestureDetector(
onTap: () {
// this is where I'm filling the list
dashboardController
.callEmployeeCheckInOutList(_employeeModel.id);
Get.dialog(
AlertDialog(
contentPadding: EdgeInsets.zero,
content: SizedBox(
height: size.height * 0.55,
width: size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
EmployeeProfileWidget(
size: size,
profileBackgroudPath: profileBackgroudPath,
employeeModel: _employeeModel,
),
// this is where my listview.builder resides
EmployeeActivityWidget(
closeCrossPath: closeCrossPath,
employeeCheckInOutList:
_employeeCheckInOutList,
employeeModel: _employeeModel,
onTap: () {},
),
],
),
),
),
);
},
My listview.builder:
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 32.0,
right: 50.0,
),
child: ListView.builder(
itemCount: employeeCheckInOutList.length,
shrinkWrap: true,
itemBuilder: (context, index) {
final _checkInOutModel = employeeCheckInOutList[index];
return SizedBox(
height: 120,
child: TimelineTile(
beforeLineStyle: const LineStyle(
color: Color(0xffa5affb),
),
My Controller:
Rx<List<CheckInOutModel>> _employeeCheckInOutList =
Rx<List<CheckInOutModel>>([]);
List<CheckInOutModel> get employeeCheckInOutList =>
_employeeCheckInOutList.value;
Future<void> callEmployeeCheckInOutList(String id) async {
_employeeCheckInOutList =
await EmployeeService.employeeCheckInOutFuture(docId: id);
update();
}
Use .assignAll method on the RxList to trigger UI update:
Future<void> callEmployeeCheckInOutList(String id) async {
final result = await EmployeeService.employeeCheckInOutFuture(docId: id);
_employeeCheckInOutList.assignAll(result);
}
And you don't need to call update() when using Rx.
I already faced same issue.
Solution:
Simply use again GetX<Controller> inside AlertDialog
like
GetX<DashboardController>(
init: Get.put<DashboardController>(DashboardController()),
builder: (DashboardController dashboardController) {
return GridView.builder(
.....
Get.dialog(
AlertDialog(
contentPadding: EdgeInsets.zero,
content: GetX<DashboardController>(
init: Get.put<DashboardController>(DashboardController()),
builder: (DashboardController dashboardController) {
SizedBox(

Future Builder not building

I'm trying to include FutureBuilder but it goes into the CircularProgressIndicator() and doesn't load the actual screen code after the value of 'time' is populated by calling from SharedPreferences and the ConnectionState is done. It just gets stuck in the CircularProgressIndicator().
What am I missing here?
Future<int> getTime() async {
await MySharedPreferences.instance.getIntValue("time_key").then((value) =>
setState(() {
time= value;
}));
return time;
#override
void initState() {
super.initState();
MySharedPreferences.instance
.getStringValue("title_key")
.then((value) => setState(() {
title = value;
}));
controller =
AnimationController(vsync: this,
duration: Duration(
seconds: time));
controller2 =
AnimationController(vsync: this,
duration: Duration(
seconds: time));
controller3 =
AnimationController(vsync: this,
duration: Duration(
seconds: 1));
....}
#override
Widget build(BuildContext context){
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
backgroundColor: Colors.black,
body: FutureBuilder<int>
(
future: getTime(),
builder: ( BuildContext context, AsyncSnapshot<int> snapshot) {
print(snapshot);
print(time);
if (snapshot.connectionState == ConnectionState.done) {
print(time);
return SafeArea(
minimum: const EdgeInsets.all(20.0),
child: Stack(
children: <Widget>[
Container(
child:
Align(
alignment: FractionalOffset.topCenter,
child: AspectRatio(
aspectRatio: 1.0,
child: Container(
height: MediaQuery
.of(context)
.size
.height / 2,
width: MediaQuery
.of(context)
.size
.height / 2,
decoration: BoxDecoration(
//shape: BoxShape.rectangle,
color: Colors.black,
image: DecorationImage(
image: AssetImage(
"assets/images/moon.png"),
fit: BoxFit.fill,
)
),
),
),
),
),
build_animation(),
],
),
);
}
else {
return CircularProgressIndicator();
}
}
),
),
);
}
build_animation() {
return AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Stack(
children: <Widget>[
Padding(
padding: EdgeInsets.all(0.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: Align(
alignment: FractionalOffset.bottomCenter,
child: AspectRatio(
aspectRatio: 1.0,
child: Stack(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top:MediaQuery.of(context).size.height / 6),
child: Column(
children: <Widget>[
Text(
title.toString(),
style: TextStyle(
fontSize: 20.0,
color: Colors.black,fontWeight: FontWeight.bold,),
),
new Container(
child: new Center(
child: new Countdown(
animation: new StepTween(
begin: time,
end: 0,
).animate(controller),
.....
For starters, you do not need to setState for the result of the Future you use with a FutureBuilder. The whole point of the FutureBuilder class is to handle that for you. Also, it's best to not mix .then() and await until you have more experience. They work well together, but concentrate at one concept at a time while you are still learning.
This is your method after it's trimmed down (your choice if that's still worth a method, or if your want to put that code into iniState directly):
Future<int> getTime() async {
final value = await MySharedPreferences.instance.getIntValue("time_key");
return value;
}
You should not give that method to your FutureBuilder, otherwise you will start it anew every time build is called for any reason.
So you initState should look like this:
Future<int> futureIntFromPreferences;
#override
void initState() {
super.initState();
futureIntFromPreferences = getTime();
}
Then you can use that in your FutureBuilder:
body: FutureBuilder<int>(
future: futureIntFromPreferences,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
For a detailed explanation, read What is a Future and how do I use it?

Using a CachedVideoPlayer in a listview

I am attempting to show videos in a listview that is preventing me from declaring the videocontroller in the initState. This causes me to accidentally be redrawing the video multiple times during the application. I am receiving this error:
FATAL EXCEPTION: ExoPlayerImplInternal:Handler
then
java.lang.OutOfMemoryError: OutOfMemoryError thrown while trying to throw OutOfMemoryError; no stack trace available
with my current implementation. It appears to work fora while but the memory slowly builds up until it is full. How can I implement this differently?
here is the code I am calling in the stream:
Widget getVideoItem(DocumentSnapshot doc) {
if (watchList.contains(doc['user'])) watched = true;
DateTime dateTime = DateTime.parse(doc['time']);
_videoPlayerController = CachedVideoPlayerController.network(doc["downUrl"])
..initialize();
_videoPlayerController.setLooping(true);
_videoPlayerController.play();
volumeOn = sharedPreferences.getBool("vidVol");
if (volumeOn == null) {
sharedPreferences.setBool("vidVol", false);
volumeOn = false;
}
if (volumeOn) {
_videoPlayerController.setVolume(1.0);
} else {
_videoPlayerController.setVolume(0.0);
}
return new FutureBuilder(
future: getUserData(doc["user"]),
builder: (BuildContext context, snapshot) {
return SizedBox(
height: MediaQuery.of(context).size.width + 140,
width: MediaQuery.of(context).size.width,
child: Column(children: <Widget>[
new ListTile(
title: new Text(userInfo),
subtitle: new Text(doc["title"]),
leading: FutureBuilder(
future: getProfUrl(doc),
builder: (BuildContext context, snapshot) {
Widget child;
if (!snapshot.hasData) {
child = _showCircularProgress();
} else {
child = child = new Container(
width: 44.0,
height: 44.0,
child: CachedNetworkImage(
imageUrl: doc["profUrl"],
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
),
);
}
return child;
}),
),
new Padding(
padding: EdgeInsets.fromLTRB(4, 4, 4, 4),
child: FutureBuilder(
future: getDownUrl(doc),
builder: (BuildContext context, snapshot) {
List<Widget> children;
if (!snapshot.hasData) {
children = [_showCircularProgress()];
} else {
children = [
Center(
child: new AspectRatio(
aspectRatio: 1 / 1,
child: Stack(
children: [
VisibilityDetector(
key: Key("unique key"),
onVisibilityChanged: (VisibilityInfo info) {
if (info.visibleFraction > .20) {
_videoPlayerController.pause();
} else {
_videoPlayerController.play();
}
},
child: CachedVideoPlayer(
_videoPlayerController,
)),
IconButton(
icon: volumeOn
? Icon(Icons.volume_up)
: Icon(Icons.volume_off),
onPressed: () {
setState(() {
_videoPlayerController.pause();
sharedPreferences.setBool(
"vidVol", !volumeOn);
});
},
),
],
),
),
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
}),
),
new Row(
children: [
new IconButton(
icon: !watched
? new Icon(
Icons.remove_red_eye,
color: Colors.black26,
)
: new Icon(
Icons.remove_red_eye,
color: Colors.blueGrey[400],
),
onPressed: () {
initToggleWatched(watchList, doc["user"], name, position,
secPosition, state, year, user);
}),
Padding(
padding: EdgeInsets.fromLTRB(5, 0, 0, 0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
dateTime.day.toString() +
"/" +
dateTime.month.toString() +
"/" +
dateTime.year.toString(),
style: TextStyle(color: Colors.black26, fontSize: 12),
),
),
),
],
)
]),
);
},
);
}
Try making the widget with a controller a separate StatefullWidget instead of putting everything in one place and manage the instantiation and disposal of the controller in the initState() and dispose() methods.