Flutter getx state is not properly matched when I reactive value - flutter

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.

Related

How to change variable value in flutter with bloc?

Want to ask is How to change variable value with stream flutter?
You think my question is so fundamental and I can search in everywhere on internet. But in this scenario with stream, I can't change the variable value with method. How I need to do? please guide me. I will show with example.
Here, this is bloc class code with rxDart.
class ChangePinBloc {
final ChangePinRepository _changePinRepository = ChangePinRepository();
final _isValidateConfirmNewPinController = PublishSubject();
String oldPin = '';
Stream get isValidateConfirmNewPinStream =>
_isValidateConfirmNewPinController.stream;
void checkValidateConfirmNewPin(
{required String newPinCode, required String oldPinCode}) {
if (newPinCode == oldPinCode) {
oldPin = oldPinCode;
changePin(newCode: newPinCode);
isValidateConfirmPin = true;
_isValidateConfirmNewPinController.sink.add(isValidateConfirmPin);
} else {
isValidateConfirmPin = false;
_isValidateConfirmNewPinController.sink.add(isValidateConfirmPin);
}
}
void changePin({required String newCode}) async {
changePinRequestBody['deviceId'] = oldPin;
}
dispose() {
}
}
Above code, want to change the value of oldPin value by calling checkValidateConfirmNewPin method from UI. And want to use that oldPin value in changePin method. but oldPin value in changePin always get empty string.
This is the calling method checkValidateConfirmNewPin from UI for better understanding.
PinCodeField(
pinLength: 6,
onComplete: (value) {
pinCodeFieldValue = value;
changePinBloc.checkValidateConfirmNewPin(
newPinCode: value,
oldPinCode: widget.currentPinCodeFieldValue!);
},
onChange: () {},
),
Why I always get empty String although assign a value to variable?
Lastly, this is complete code that calling state checkValidateConfirmNewPin from UI.
void main() {
final changePinBloc = ChangePinBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: StreamBuilder(
stream: changePinBloc.isValidateConfirmNewPinStream,
builder: (context, AsyncSnapshot pinValidateSnapshot) {
return Stack(
children: [
Positioned.fill(
child: Column(
children: [
const PinChangeSettingTitle(
title: CONFIRM_NEW_PIN_TITLE,
subTitle: CONFIRM_NEW_PIN_SUBTITLE,
),
const SizedBox(
height: margin50,
),
Padding(
padding: const EdgeInsets.only(
left: margin50, right: margin50),
child: PinCodeField(
pinLength: 6,
onComplete: (value) {
changePinBloc.checkValidateConfirmNewPin(
newPinCode: value,
oldPinCode: widget.newCodePinValue!,
);
},
onChange: () {},
),
)
],
),
),
pinValidateSnapshot.hasData
? pinValidateDataState(pinValidateSnapshot, changePinBloc)
: const Positioned.fill(
child: SizedBox(),
),
],
);
},
),
),
);
}
}
To update the variable you should emit a new state using emit() method.
Just make sure your bloc is correct as it should inherit from Bloc object. Read flutter_bloc documentation to know how to use it.
A simple example:
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(ExampleInitial()) {
on<ExampleEvent>((event, emit) {
//Do some logic here
emit(ExampleLoaded());
});
}
}

Flutter GetX tagged controller data update

First of all I don't know what i am facing, but I'll do my best to explain the situation.
I'm trying to build chat app and i have two sections on same page. These two different sections are rendering inside same ListView. Only thing that changing is the data which i am using to feed the ListView. I need to get the status of user in real time so i am putting tagged controllers for each tile which is rendering inside list view. Here comes the problem. The tiles rendered at the same index are not showing the true states of themselves until some state changes on that tile for example position of any Stack item.
Here is the code.
In this part I'm rendering ListView
ListView.builder(
itemCount: chatController.currentChats!.length,
itemBuilder: (context, index) {
return GetBuilder<UserOnlineController>(
global: false,
init: Get.find<UserOnlineController>(tag: chatController.currentUserID == chatController.currentChats![index].user1 ? chatController.currentChats![index].user2 : chatController.currentChats![index].user1),
builder: (userController) {
return Stack(
children: [
Positioned(
child: Container(
color: Colors.black,
width: Get.width,
height: Dimensions.h100,
child: Center(
child: Text(
"${userController.user!.name!}",
style: TextStyle(
color: Colors.white
),
),
),
)
)
],
);
}
);
}),
This is the part that I'm putting controllers and listening chats in real time.
void listenChats() async {
var chatController = Get.find<ChatController>();
var messagesController = Get.find<MessagesController>();
String userID = Get.find<SharedPreferenceService>().getUserID();
var currentUserDoc = (await firestoreService.getCollection('users').where('userID', isEqualTo: userID).get()).docs[0];
Stream<DocumentSnapshot> userStream = firestoreService.getCollection('users').doc(currentUserDoc.id).snapshots();
Stream<QuerySnapshot> chatStream = firestoreService.getCollection('chats').snapshots();
await for(var user in userStream){
var userObject = UserModel.fromJson(user.data() as Map<String,dynamic>);
await for(var chats in chatStream) {
List<Chat> activeChats = [];
List<Chat> unActiveChats = [];
List<Chat> newMatches = [];
List<Chat> allChats = [];
var filteredChats = chats.docs.where((chat) => userObject.chat!.active_chats!.contains(chat['chatID'])).toList();
filteredChats.forEach((chatDoc) {
var currentChat = Chat.fromJson(chatDoc.data() as Map<String,dynamic>);
if(currentChat.user1 == userID){
Get.put(
UserOnlineController(firestoreService: firestoreService, userID: currentChat.user2!),
tag: currentChat.user2!,
);
}
else{
Get.put(
UserOnlineController(firestoreService: firestoreService, userID: currentChat.user1!),
tag: currentChat.user1!
);
}
allChats.add(currentChat);
if(currentChat.isActive!){
if(currentChat.isStarted!){
activeChats.add(currentChat);
}
else{
newMatches.add(currentChat);
}
}
else{
unActiveChats.add(currentChat);
}
});
messagesController.generatePositions(activeChats.length, true);
messagesController.generatePositions(unActiveChats.length, false);
chatController.setAllChats(allChats);
chatController.setCurrentChats();
chatController.setChats(activeChats, unActiveChats, newMatches);
}
}
}
And this is the part that I'm using to control the UI state
void setAllChats(List<Chat> allChats) {
_allChats = allChats;
}
void setCurrentChats() {
_currentChats = _allChats!.where((chat) => chat.isActive! == isActiveMessages).toList();
update();
}
void setIsActiveMessages(bool state){
_isActiveMessages = state;
_currentChats = _allChats!.where((chat) => chat.isActive! == state).toList();
update();
}
In the above pictures all of these users are different but only true one is the third one at second screen shot.
Hello again this question basically explains all the details.
Multiple Instance with GetX tag not working in flutter
Basically you need to add key parameter.
GetBuilder<UserChatController>(
key: Key(currentUserControllerTag),
tag: currentUserControllerTag,
global: false,
init: Get.find<UserChatController>(tag: currentUserControllerTag),
builder: (controller) {
return controller.user != null ? Container(
width: Get.width,
height: Dimensions.h100,
child: Stack(
children: [
Positioned(
left: 0,
right: 0,
child: Container(
height: Dimensions.h100,
width: double.maxFinite,
color: Colors.black,
child:Center(
child: Text(
controller.user != null ? controller.user!.name! : "",
style: TextStyle(
color: Colors.white
),
),
)
))
],
),
) : Container();
},
)

I have problem with using condition to display an image

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.

Flutter FutureProvider Value Not Updating In Builder Method

The Problem
I am building a basic app in Flutter that gets the user's location and displays nearby places in a swipe-card format similar to Tinder. I managed to implement geolocation however when using FutureProvider/Consumer I'm experiencing a weird bug where the user's relative distance to the place is overwritten with the first distance value in the card deck. Although I am new to flutter and the Provider package, I believe there is a simple fix to this.
Side note: After searching around on Google, I attempted to use FutureProvider.value() to prevent the old value from updating but had no luck.
Thank you in advance for any assistance or direction!
A Quick Demo
Packages Used
card_swipe.dart
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:provider/provider.dart';
import 'package:swipe_stack/swipe_stack.dart';
import '../services/geolocator_service.dart';
import '../models/place.dart';
class CardSwipe extends StatelessWidget {
#override
Widget build(BuildContext context) {
final _currentPosition = Provider.of<Position>(context);
final _placesProvider = Provider.of<Future<List<Place>>>(context);
final _geoService = GeoLocatorService();
return FutureProvider(
create: (context) => _placesProvider,
child: Scaffold(
backgroundColor: Colors.grey[300],
body: (_currentPosition != null)
? Consumer<List<Place>>(
builder: (_, places, __) {
return (places != null)
? Column(
children: [
SizedBox(height: 10.0),
Container(
margin: EdgeInsets.only(top: 120.0),
height: 600,
child: SwipeStack(
children: places.map((place) {
return SwiperItem(builder:
(SwiperPosition position,
double progress) {
return FutureProvider(
create: (context) =>
_geoService.getDistance(
_currentPosition.latitude,
_currentPosition.longitude,
place.geometry.location.lat,
place.geometry.location.lng),
child: Consumer<double>(
builder: (_, distance, __) {
return (distance != null)
? Center(
child: Card(
child: Container(
height: 200,
width: 200,
child: Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment
.center,
children: [
Text(place.name),
Text(
'${(distance / 1609).toStringAsFixed(3)} mi'), // convert meter to mi
],
),
),
),
),
)
: Container();
}),
);
});
}).toList(),
visibleCount: 3,
stackFrom: StackFrom.Top,
translationInterval: 6,
scaleInterval: 0.03,
onEnd: () => debugPrint("onEnd"),
onSwipe: (int index, SwiperPosition position) =>
debugPrint("onSwipe $index $position"),
onRewind:
(int index, SwiperPosition position) =>
debugPrint("onRewind $index $position"),
),
),
],
)
: Center(
child: CircularProgressIndicator(),
);
},
)
: Center(
child: CircularProgressIndicator(),
),
),
);
}
}
geolocator_service.dart
import 'package:geolocator/geolocator.dart';
class GeoLocatorService {
final geolocator = Geolocator();
Future<Position> getLocation() async {
return await geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
locationPermissionLevel: GeolocationPermission.location,
);
}
Future<double> getDistance(
double startLat, double startLng, double endLat, double endLng) async {
return await geolocator.distanceBetween(startLat, startLng, endLat, endLng);
}
}
place.dart
Quick note: Place class does import a custom class called geometry.dart however this is purely for structuring the Place object and I'm certain it doesn't affect the bug. Therefore, it has been omitted.
import './geometry.dart';
class Place {
final String name;
final Geometry geometry;
Place(this.name, this.geometry);
Place.fromJson(Map<dynamic, dynamic> parsedJson)
: name = parsedJson['name'],
geometry = Geometry.fromJson(
parsedJson['geometry'],
);
}
You have to add a key to the SwiperItem with some unique value (like the name of the place) since currently flutter thinks that the widget has stayed the same so the Consumer gets the state of the old topmost widget.
By adding the key you tell flutter that you removed the topmost widget and the new topmost is in fact the second widget

Question about Flutter State and retrieving variables from State vs StatefulWidget

Here's the context:
In my app, users can create a question, and all questions will be displayed on a certain page. This is done with a ListView.builder whose itemBuilder property returns a QuestionTile.
The problem:
If I create a new question, the text of the new question is (usually) displayed as the text of the previous question.
Here's a picture of me adding three questions in order, "testqn123", "testqn456", "testqn789", but all are displayed as "testqn123".
Hot restarting the app will display the correct texts for each question, but hot reloading wont work.
In my _QuestionTileState class, if I change the line responsible for displaying the text of the question on the page, from
child: Text(text)
to
child: Text(widget.text)
the issue will be resolved for good. I'm not super familiar with how hot restart/reload and state works in flutter, but can someone explain all of this?
Here is the code for QuestionTile and its corresponding State class, and the line changed is the very last line with words in it:
class QuestionTile extends StatefulWidget {
final String text;
final String roomName;
final String roomID;
final String questionID; //
QuestionTile({this.questionID, this.text, this.roomName, this.roomID});
#override
_QuestionTileState createState() => _QuestionTileState(text);
}
class _QuestionTileState extends State<QuestionTile> {
final String text;
int netVotes = 0;
bool expand = false;
bool alreadyUpvoted = false;
bool alreadyDownvoted = false;
_QuestionTileState(this.text);
void toggleExpansion() {
setState(() => expand = !expand);
}
#override
Widget build(BuildContext context) {
RoomDbService dbService = RoomDbService(widget.roomName, widget.roomID);
final user = Provider.of<User>(context);
print(widget.text + " with questionID of " + widget.questionID);
return expand
? ExpandedQuestionTile(text, netVotes, toggleExpansion)
: Card(
elevation: 10,
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 7, 15, 7),
child: GestureDetector(
onTap: () => {
Navigator.pushNamed(context, "/ChatRoomPage", arguments: {
"question": widget.text,
"questionID": widget.questionID,
"roomName": widget.roomName,
"roomID": widget.roomID,
})
},
child: new Row(
// crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Column(
// the stack overflow functionality
children: <Widget>[
InkWell(
child: alreadyUpvoted
? Icon(Icons.arrow_drop_up,
color: Colors.blue[500])
: Icon(Icons.arrow_drop_up),
onTap: () {
dynamic result = dbService.upvoteQuestion(
user.uid, widget.questionID);
setState(() {
alreadyUpvoted = !alreadyUpvoted;
if (alreadyDownvoted) {
alreadyDownvoted = false;
}
});
},
),
StreamBuilder<DocumentSnapshot>(
stream: dbService.getQuestionVotes(widget.questionID),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
// print("Current Votes: " + "${snapshot.data.data["votes"]}");
// print("questionID: " + widget.questionID);
return Text("${snapshot.data.data["votes"]}");
}
},
),
InkWell(
child: alreadyDownvoted
? Icon(Icons.arrow_drop_down,
color: Colors.red[500])
: Icon(Icons.arrow_drop_down),
onTap: () {
dbService.downvoteQuestion(
user.uid, widget.questionID);
setState(() {
alreadyDownvoted = !alreadyDownvoted;
if (alreadyUpvoted) {
alreadyUpvoted = false;
}
});
},
),
],
),
Container(
//color: Colors.red[100],
width: 290,
child: Align(
alignment: Alignment.centerLeft,
child: Text(text)), // problem solved if changed to Text(widget.text)
),
}
}
You can wrap your UI with a Stream Builder, this will allow the UI to update every time any value changes from Firestore.
Since you are using an item builder you can wrap the widget that is placed with the item builder.
That Should update the UI