I have problem with using condition to display an image - flutter

I am using image picker to get image from user then displayed in "CreateCard" UI.
The issue occur when i try using condition in my UI file, i need the condition so i can check if the file image is null before i can display it.
I am working with flutter GetX..
"CreateCard "UI Code:
GetBuilder<CreateCardContollerImp>(builder: (controller) =>UploadImage(
ontap: () {
showModalBottomSheet(
context: context,
builder: (context) {
return CreateCardBottomSheet(
uploadImageGallery: () {
controller.uploadImageGallery();
});
});
},
image: controller.image == null // Error occur here !
? AssetImage(AppImageAsset.profileimage)
: FileImage(controller.image),
),),
"UploadImage" Deifiniton:
class UploadImage extends StatelessWidget {
final void Function() ontap;
final ImageProvider<Object> image;
const UploadImage({super.key, required this.ontap, required this.image});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: ontap,
child: Stack(children: [
Container(
width: 170,
height: 170,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
offset: Offset(0, 0),
color: AppColor.primaryColor,
blurRadius: 0,
),
],
borderRadius: BorderRadius.circular(100),
),
padding: const EdgeInsets.symmetric(vertical: 5),
child: CircleAvatar(
backgroundImage: image,
radius: MediaQuery.of(context).size.width - 310,
),
),
]),
);
}
}
"CreateCard" Controller:
class CreateCardContollerImp extends CreateCardContoller {
GlobalKey<FormState> formstate = GlobalKey<FormState>();
final imagepicker = ImagePicker();
late String imagePath;
late File image;
#override
uploadImageGallery() async {
final pickedimage = await imagepicker.getImage(source: ImageSource.gallery);
if (pickedimage != null) {
image = File(pickedimage.path);
imagePath = pickedimage.path;
update();
} else {
printError(info: "No image selected");
}
}
I was expecting this method will work fine.

you're logic looks fine, try casting the as ImageProvider:
controller.image == null
? AssetImage(AppImageAsset.profileimage) as ImageProvider
: FileImage(controller.image) as ImageProvider,

Another alternative is to try just using ImageProvider directly without unnecessary casting.
controller.image == null
? AssetImage(AppImageAsset.profileimage)
: NetworkImage(controller.image),
TheAssetImage is used in getting the avatar profile image from the app's asset.
The NetworkImage is used in getting the avatar profile image from the online DB or API.

Related

StreamBuilder not displaying fetched data from MongoDB database in Flutter

I am trying to implement streambuilder without Firebase, using a MongoDB database. The aim is to build a simple chat app, live streaming the messages. So far, the live streaming when I click on the send button works since I see the message displayed in the UI. I also push that message to my DB successfully.
The problem strives when I try to display the messages fetched from my datbase. They are fetched correctly, but not displayed.
final StreamController<ChatMessageModel> _chatMessagesStreamController =
StreamController<ChatMessageModel>.broadcast();
final Stream<ChatMessageModel> _chatMessagesStream =
_chatMessagesStreamController.stream;
class MessagesStream extends StatefulWidget {
var usermail;
var usermailTo;
var title;
MessagesStream(this.usermail, this.usermailTo, this.title);
#override
_MessagesStreamState createState() => _MessagesStreamState();
}
class _MessagesStreamState extends State<MessagesStream> {
final List<ChatMessageModel> _allMessagesContainedInTheStream = [];
Future<List<dynamic>>? futureMessages;
Future fetchMessagesFromBack4App(
String usermail, String usermailTo, String dogName) async {
final queryBuilder = QueryBuilder(ParseObject('Messages'))
..whereEqualTo('sender', usermail)
..whereEqualTo('receiver', usermailTo)
..whereEqualTo('dogname', dogName)
..orderByAscending('date');
final response = await queryBuilder.query();
if (response.success && response.results != null) {
for (var message in response.results!) {
//check if message was already put into stream
bool messageFoundInAllMessageLogged = false;
for (int i = 0; i < _allMessagesContainedInTheStream.length; i++) {
if (message["sender"] == _allMessagesContainedInTheStream[i].sender &&
message["receiver"] ==
_allMessagesContainedInTheStream[i].receiver &&
message["date"] == _allMessagesContainedInTheStream[i].date &&
message["dogname"] ==
_allMessagesContainedInTheStream[i].dogname &&
message["message"] ==
_allMessagesContainedInTheStream[i].message) {
messageFoundInAllMessageLogged = true;
break;
}
}
// Add message to stream if it was not logged yet
if (!messageFoundInAllMessageLogged) {
ChatMessageModel chatMessageModelRecord = ChatMessageModel(
receiver: message["receiver"],
message: message["message"],
sender: message["sender"],
dogname: message["dogname"],
date: DateTime.parse(message["date"]));
_allMessagesContainedInTheStream.add(chatMessageModelRecord);
debugPrint("putting message to stream: " + message['message']);
}
}
} else {
return [];
}
}
#override
void initState() {
fetchMessagesFromBack4App(widget.usermail, widget.usermailTo, widget.title);
_chatMessagesStream.listen((streamedMessages) {
// _allMessagesContainedInTheStream.clear();
debugPrint('Value from controller: $streamedMessages');
_allMessagesContainedInTheStream.add(streamedMessages);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<ChatMessageModel>(
stream: _chatMessagesStream,
builder: (context, snapshot) {
return Expanded(
child: ListView.builder(
// reverse: true,
padding:
const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
itemCount: _allMessagesContainedInTheStream.length,
itemBuilder: (BuildContext context, int index) {
if (snapshot.hasData) {
return UserChatBubble(
chatMessageModelRecord:
_allMessagesContainedInTheStream[index],
);
} else {
print(snapshot.connectionState);
return Container();
}
},
),
);
},
);
}
}
class UserChatBubble extends StatelessWidget {
final ChatMessageModel chatMessageModelRecord;
const UserChatBubble({
Key? key,
required this.chatMessageModelRecord,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 5,
horizontal: 5,
),
child: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 7 / 10,
),
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(15.0),
topLeft: Radius.circular(15.0),
),
color: primaryColor,
),
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 20,
),
child: ListTile(
title: Text("${chatMessageModelRecord.message}"),
subtitle: Text(chatMessageModelRecord.date.toString()),
),
),
),
],
);
}
}
The method fetchMessagesFromBack4App fetches correctly the data and add records to _allMessagesContainedInTheStream. However, when the method ends this _allMessagesContainedInTheStream list is empty (despite of inside the method is adding records). Therefore, snapshot is empty too.
Only when I press the send button then I am able to see all the messages: the fetched ones and the sent ones.
Summarizing: snapshot has no data when I navigate to my chat screen. It receives the data only when I press the send button to send a message.
_chatMessagesStream.listen() will listen to the _chatMessagesStream stream and if any event occur, everything in the block will execute.
your fetchMessagesFromBack4App() does not emit any new event to the above stream, but add value to _allMessagesContainedInTheStream which is a List
to sum up you need to change _allMessagesContainedInTheStream.add(chatMessageModelRecord); to _chatMessagesStreamController.add(chatMessageModelRecord) add new event to the stream in other for your StreamBuilder to rebuild

How can I build a widget conditionally, based on various different futures that could be called from a few buttons?

I have created a reusable CircleAvatar widget which builds itself based on it's parameters. The parameters are using .watch() from riverpod & so update automatically. This translates to, in rough pseudo code:
No data (first/last name null && null avatar photo).....=> blank user icon avatar
Avatar image null && first/last name not null...........=> users initials in circle avatar
Avatar image not null...................................=> image avatar
Now I have three buttons, each button changes state which changes the parameters for the avatar as above. The buttons include futures like so:
Take picture.....=> image
Get photo file...=> image
Use initials.....=> image null (returns text initial avatar)
Problem
I don't have a clue how to architect this into future builder. The "takePicture" & "getPhotoFile" methods need a future builder minimally since they take some time. I can't use FutureBuilder(future: [futureOne, futureTwo, futureThree])) because not all futures should be called at once...
To be clear, my code is working right now. But crashes on strange situation going back and forth from initial avatar to image avatar. And loading image takes some time.
How can something like this be designed into a future builder or similar concept?
Reactive avatar widget:
class ActiveAvatarCircle extends StatefulWidget {
const ActiveAvatarCircle({
Key? key,
required double? radius,
required String? initials,
required ImageProvider<Object>? avatar,
}) : _radius = radius,
_initials = initials,
_avatar = avatar,
super(key: key);
final double? _radius;
final String? _initials;
final ImageProvider<Object>? _avatar;
#override
State<ActiveAvatarCircle> createState() => _ActiveAvatarCircleState();
}
class _ActiveAvatarCircleState extends State<ActiveAvatarCircle> {
#override
Widget build(BuildContext context) {
// Default blank circle avatar - awaiting users initials
if (widget._initials == null && widget._avatar == null) {
return CircleAvatar(
backgroundColor: ThemeEndpoints.avatarBackgroundColor(),
foregroundColor: ThemeEndpoints.avatarForegroundColor(),
radius: widget._radius ?? 80,
child: ThemeEndpoints.avatarDefaultIcon(),
);
}
// Initialed circle avatar awaiting avatar choice/no choice
if (widget._initials != null && widget._avatar == null) {
return CircleAvatar(
radius: widget._radius ?? 80,
backgroundColor: ThemeEndpoints.avatarBackgroundColor(),
foregroundColor: ThemeEndpoints.avatarForegroundColor(),
child: Text(
widget._initials!,
style: ThemeEndpoints.avatarTextTheme(),
),
);
}
// Avatar selected and overriding both default & initials avatar
return CircleAvatar(
radius: widget._radius ?? 80,
foregroundImage: widget._avatar,
);
}
}
Avatar section in my form code:
// Avatar section
Widget _avatarSection(
SignUpState signUpState,
BuildContext context,
String? firstName,
String? lastName,
ImageProvider<Object>? avatar,
) {
return SizedBox(
height: 150,
child: SizedBox(
width: 90.w,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ActiveAvatarCircle(
radius: null,
initials: (firstName != null && lastName != null)
? '${firstName[0]}${lastName[0]}'
: null,
avatar: avatar,
),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CustomButton(
buttonText: TextContent.of(context).authFormAvatarChoosePhotoText,
onPressed: () => _onGetImageFromFilesPressed(signUpState),
width: SizeConfig.screenWidth! * 0.40,
),
CustomButton(
buttonText: TextContent.of(context).authFormAvatarTakePhotoText,
onPressed: () => _onGetImageFromCameraPressed(signUpState),
width: SizeConfig.screenWidth! * 0.40,
),
CustomButton(
buttonText: TextContent.of(context).authFormAvatarInitialledText,
onPressed: () => _onUseInitialledAvatarPressed(signUpState),
width: SizeConfig.screenWidth! * 0.40,
),
],
),
],
),
),
);
}
// On get image from files pressed
Future<void> _onGetImageFromFilesPressed(SignUpState signUpState) async {
XFile? pickedFile = await ImagePicker().pickImage(
source: ImageSource.gallery,
maxWidth: 288,
maxHeight: 288,
imageQuality: 100,
);
if (pickedFile != null) {
final Uint8List bytes = await pickedFile.readAsBytes();
final MemoryImage image = MemoryImage(bytes);
signUpState.setAvatarStorageFormat(base64Encode(bytes));
signUpState.setAvatar(image);
pickedFile = null;
}
}
// On get get avatar from camera pressed
Future<void> _onGetImageFromCameraPressed(SignUpState signUpState) async {
XFile? cameraImage = await ImagePicker().pickImage(
source: ImageSource.camera,
maxWidth: 288,
maxHeight: 288,
imageQuality: 100,
);
if (cameraImage != null) {
final Uint8List bytes = await cameraImage.readAsBytes();
final MemoryImage image = MemoryImage(bytes);
signUpState.setAvatar(image);
signUpState.setAvatarStorageFormat(base64Encode(bytes));
}
}
// On use initialled avatar pressed
Future<void> _onUseInitialledAvatarPressed(SignUpState signUpState) async {
signUpState.setAvatar(null);
}

Flutter getx state is not properly matched when I reactive value

I'm trying to set random network images to flutter app by getX.
The problem is when I activate 'nextPhoto', it printed next links in my list.
However, image widget didn't change to next photo.
Output example : first photo = cat1.jpg , next photo = cat1.jpg
Expected example : first photo = cat1.jpg, next photo = cat2.jpg
Someone helps me will be highly appreciated.
import ...
class ProfileController extends GetxController {
List<String> imageList = [
'https://1.jpg'
'https://2.jpg',
'https://3.jpg',
'https://4.jpg',
'https://5.jpg',
];
late List<String> randomImage = imageList.toList()..shuffle();
int imageNumber = 0;
late RxString imagePath = randomImage[imageNumber].obs;
RxBool isEditMyProfile = false.obs;
#override
void onInit() {...}
void toggleEditProfile() {...}
void savePhoto() async {...}
void nextPhoto() {
print('Next Photo');
imageNumber != imageList.length ? imageNumber++ : imageNumber == 0;
imagePath = randomImage[imageNumber].obs;
imageCache.clear();
update();
}
}
Widget _profileImage() {
return GestureDetector(
onTap: () {
controller.toggleEditProfile();
print('change my Image!');
},
child: Container(
padding: EdgeInsets.all(10),
width: Get.mediaQuery.size.width,
height: Get.mediaQuery.size.height,
child: FittedBox(
child: Obx(
() => Image.network(
controller.imagePath.value,
fit: BoxFit.fill,
),
),
),
),
);
}
Save new value insteads of create new obs instance.
ps: look like your randomly imageNumber isn't random actually... nvm
// late RxString imagePath = randomImage[imageNumber].obs;
=> late imagePath = Rx<String>(randomImage[imageNumber]);
// imagePath = randomImage[imageNumber].obs;
=> imagePath.value = randomImage[imageNumber];
Try
Widget _profileImage() {
return GestureDetector(
onTap: () {
controller.nextPhoto();
print('change my Image!');
},
child: Container(
padding: EdgeInsets.all(10),
width: Get.mediaQuery.size.width,
height: Get.mediaQuery.size.height,
child: FittedBox(
child: Obx(
() => Image.network(
controller.imagePath.value,
fit: BoxFit.fill,
),
),
),
),
);
}
or share what's inside toggleEditProfile function.

I tried to upload multi images, but it wasn't display preview photos

I tried to upload multi images, but it wasn't display preview photos.
Original code is can display only photo, but I modified the code then tried to upload multi images. Still cannot show to me.
Original Code, working well, but just show one image
SizedBox(
height: 250,
child: AspectRatio(
aspectRatio: 487 / 451,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: MemoryImage(_file!),
fit: BoxFit.fill,
alignment: FractionalOffset.topCenter,
),
),
),
),
),
Then I tried to modified to this one
Expanded(
child: GridView.builder(
itemCount: selectedFiles.length,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return Image.file(File(selectedFiles[index].path));
},
),
),
It wasn't show to me.
I can got the image list
Future<void> selectImage() async {
if (selectedFiles != null) {
selectedFiles.clear();
}
try {
final List<XFile>? imgs = await _picker.pickMultiImage();
if (imgs!.isNotEmpty) {
selectedFiles.addAll(imgs);
}
print("image list : " + imgs.length.toString());
} catch (e) {
print(e.toString());
}
setState(() {});
}
Or I need to modify this code??
SimpleDialogOption(
padding: const EdgeInsets.all(20),
child: const Text('Choose from gallery'),
onPressed: () async {
Navigator.of(context).pop();
Uint8List file = await pickImage(ImageSource.gallery);
// final List<XFile>? imgs = await _picker.pickMultiImage();
// if (imgs!.isNotEmpty) {
// selectedFiles.addAll(imgs);
// }
setState(() {
_file = file;
});
},
),
For the GridView to display images the Ui has to rebuild
So when you add images to your list
if (imgs!.isNotEmpty) {
selectedFiles.addAll(imgs);
}
you dont notify the UI to rebuild.
you can call an empty setstate below the selectedFiles to force UI to rebuild.
if (imgs!.isNotEmpty) {
selectedFiles.addAll(imgs);
setState((){
})
}
For example when picking a single file
File? myfile;
pickFile()async{
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) {
File file = File(result.files.single.path);
setState((){
myFile=file;
})
} else {
// User canceled the picker
}}

Flutter/Dart - How to update Image after Image Picker on other Screens

I'm using Flutter's Image Picker plugin in order to allow the user to change their avatar. When they go to their account page they see their regular avatar photo with a camera icon on top. Clicking the camera icon will allow them to either take a photo from their camera or choose a new avatar from their gallery. After choosing the new one, the avatar photo automatically updates. However, when navigating away from their account page, the old avatar is visible throughout the rest of the app. I'm using Provider with a Change Notifier and Consumers for Avatars everywhere else. The problem though is that I can only access the Provider within a build so I don't know where I can call the Provider in my code. Add to this the fact that the Avatar I'm using all around the app comes from an internet url. After choosing with Image Picker, the new avatar photo gets uploaded to a server. The name of the new photo replaces the name of the old photo. Hence my app doesn't even know anything changed. Even reloading the pages doesn't work. However if I hot restart my app, the new avatar photo appears. Any ideas what I can do?
Here's the Image Picker code;
class Picker extends StatefulWidget {
Picker({Key key, this.title}) : super(key: key);
final String title;
#override
_PickerState createState() => _PickerState();
}
class _PickerState extends State<Picker>
with TickerProviderStateMixin,ImagePickerListener{
File _image;
AnimationController _controller;
ImagePickerHandler imagePicker;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
imagePicker= ImagePickerHandler(this,_controller);
imagePicker.init();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var socialProvider = Provider.of<SocialProvider>(context);
return Container(
child: GestureDetector(
onTap: () => imagePicker.showDialog(context),
child: Center(
child: Stack(
children: <Widget>[
Center(
child: _image == null?
Consumer<SocialProvider>(
builder: (context, socialProvider, child) {
return
Image.network(socialProvider.currentavatar,
width: 200,
height: 200,
);
}) :
Container(
height: 200.0,
width: 200.0,
decoration: BoxDecoration(
color: Colors.grey,
image: DecorationImage(
image: FileImage(_image),
fit: BoxFit.cover,
),
),
),
),
Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Container(
color: Colors.black26,
child: Icon(Icons.camera_alt,
color: Colors.white,
size: 40,
),
),
),
),
,
)
),
),
);
}
#override
userImage(File _image) async{
setState(() {
this._image = _image;
});
}
}
Currently the Consumers are correctly updating the avatars throughout the app whenever a user obtains a new avatar through logging in via social media. The new avatar is uploaded to the server and the ChangeNotifier is informed. The code for the Provider here is ;
Future<void> postSocialData(String avatar) async {
final url = "http://example.com/example.php&currentavatar=" + $avatar;
final response = await http.get(url);
if (response.statusCode == 200) {
currentavatar = "http://example.com/user.jpg";
var box = await Hive.openBox('currentuser');
box.put('currentavatar', "http://example.com/user.jpg",);
notifyListeners();
}
}
So I tried putting this into my Provider and calling it from an onTap function in the Image Picker build. Here's the onTap function;
GestureDetector(
onTap: () async {
String avatar = await _listener.openGallery(socialProvider.currentuserid);
String updatedavatar = "http://example.com/" + avatar;
socialProvider.updateAvatar(updatedavatar);
},
child: roundedButton(
"Gallery",
EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
const Color(0xFF167F67),
const Color(0xFFFFFFFF)),
),
And here's the Provider it calls;
Future<void> updateAvatar(String avatar) async {
var box = await Hive.openBox('currentuser');
box.put('currentavatar', avatar);
currentavatar = avatar;
notifyListeners();
}
But that didn't update the consumers with the new avatar. I guess because the external url for the avatar hasn't changed as the photo has simply been replaced and keeps the same name.
Using Hive's listener was a good idea. But it didn't help because it turns out the uploaded image - having the same URL as the replaced image - isn't being refreshed on the server-side. So the caching must be sorted out on the web server.