I have a page which is connected to appstate and viewmodel ,
what i'm doing in this page is picking an image from gallery and then sending it to the server , when i send i prompt a message to the user so he can see the loading ,
when the upload epic ends there is an action than goes to the reducer which change the state so the ui in the page will change ,
the reducer receives it and return a state , but the onDidChange not prompt right after ,
here's some code:
page build method :
#override
Widget build(BuildContext context) {
return new StoreConnector<AppState, AddRefundViewModel>(
converter: addRefundConverter,
onInitialBuild: (AddRefundViewModel vm) => {vm.doInitializeDto()},
builder: (context, vm) {
return MaterialApp(
title: 'Test',
home: Scaffold(
body: Column(
children: <Widget>[
Container(
child: new Column(
children: <Widget>[
RefundHeader1(context, vm),
RefundHeader(context, vm),
],
),
decoration: new BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerRight,
end: Alignment.bottomLeft,
colors: [primary, primaryGradient]),
),
width: MediaQuery.of(context).size.width * 1,
height: MediaQuery.of(context).size.width * 0.4,
),
//Text(vm.c),
refundRowSum(vm),
refundRowDate(context, vm),
refundRowCurrent(context, vm),
refundRowRefund(context, vm),
refundRowAtm(context, vm),
Expanded(child: Container(
child: GestureDetector(onTap: () {
//print("tap");}
// myFocusNode.dispose();},
if (myFocusNode != null) {
FocusScope.of(context).requestFocus(new FocusNode());
}
}),
)),
proceedMessage(context, vm),
],
),
bottomNavigationBar: BottomAppBar(
child: proceedButton(context, vm),
),
resizeToAvoidBottomPadding: false,
));
},
onDidChange: (vm) {
print('---!!!!-----<<<<<<<<<<<<<< onDidChange >>>>>>>>>>>-----!!!------');
if (vm.addRefundDto.tmpFileStat == TmpFileStat.BadExt) {
showSendReceiptDialog2(context, TmpFileStat.BadExt);
vm.doInitTmpFileStat();
} else if (vm.addRefundDto.tmpFileStat == TmpFileStat.TooLarge) {
showSendReceiptDialog2(context, TmpFileStat.TooLarge);
vm.doInitTmpFileStat();
}
else if (vm.addRefundDto.uploadRefundStatus ==
RefundUpload.Success ||vm.addRefundDto.uploadRefundStatus ==
RefundUpload.Started ||
vm.addRefundDto.uploadRefundStatus == RefundUpload.Failed) {
Navigator.pop(context);
showSendReceiptDialog(context, vm.addRefundDto.uploadRefundStatus);
}
},
);}
reducer :
case GotRefund:
print('----=== GotRefund add new refund reducer ===-----');
AddRefundDto refundDto = state.refundDto;
refundDto.uploadRefundStatus=RefundUpload.Success;
state.refundDto= refundDto;
return new AddRefundState(
fileDto: null,
byteImage: null,
refundDto: state.refundDto,
);
ADDED ViewModel :
import 'package:flutter/widgets.dart';
import 'package:iai/models/add_refund_dto.dart';
import 'package:iai/store/ui/ui_state.dart';
import 'package:iai/theme/images.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:iai/models/currency.dart';
class AddRefundViewModel {
bool fileAttachment;
DateTime pickedDate;
double amount;
bool isAtm;
bool canProceed;
AddRefundDto addRefundDto;
//final List<String> refundList;
final Map<String, AssetImage> refundList;
final Map<String, IconData> currencyList;
final List<Currency> currencyList2;
Function saveCurrentViewModel;
Function sendDtoToServer;
Function openGallery;
Function openCamera;
Function doInitializeDto;
Function doRemoveTmpImage;
Function doInitTmpFileStat;
final Function search;
final String searchTerm;
final bool showSearch;
AddRefundViewModel({
this.openCamera,
this.openGallery,
this.addRefundDto,
this.search,
this.searchTerm,
this.showSearch,
this.doInitializeDto,
this.doRemoveTmpImage,
this.doInitTmpFileStat,
this.refundList,
this.sendDtoToServer,
this.saveCurrentViewModel,
this.pickedDate,
this.amount,
this.currencyList,
this.isAtm,
this.canProceed,
this.currencyList2,
this.fileAttachment,
});
}
thanks to everyone that can contribute
Can you also post your AddRefundViewModel class in here? I think the on onDidChange is called based on those variables.
Related
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());
});
}
}
the dart code below displays a web pdf by authenticating with a token to a backend via the webviewx pub: , the token however on android is not passed and the page displays an error of authentication, what should I change to ensure that the token is also passed on android? How to solve this error by WebViewX?
Dart Code:
import 'dart:developer';
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:webviewx/webviewx.dart';
class WebViewXPage extends StatefulWidget {
final Url, Token;
WebViewXPage(this.Url, this.Token);
#override
_WebViewXPageState createState() => _WebViewXPageState();
}
class _WebViewXPageState extends State<WebViewXPage> {
late WebViewXController webviewController;
final initialContent =
'<h4> This is some hardcoded HTML code embedded inside the webview <h4> <h2> Hello world! <h2>';
final executeJsErrorMessage =
'Failed to execute this task because the current content is (probably) URL that allows iframe embedding, on Web.\n\n'
'A short reason for this is that, when a normal URL is embedded in the iframe, you do not actually own that content so you cant call your custom functions\n'
'(read the documentation to find out why).';
Size get screenSize => MediaQuery.of(context).size;
#override
void dispose() {
webviewController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
padding: const EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
Container(
decoration: BoxDecoration(
border: Border.all(width: 0.2),
),
child: _buildWebViewX(),
),
],
),
),
),
);
}
Widget _buildWebViewX() {
return WebViewX(
key: const ValueKey('webviewx'),
initialContent: widget.Url,
initialSourceType: SourceType.html,
height: screenSize.height - 150,
width: screenSize.width,
onWebViewCreated: (controller) =>{ webviewController = controller, _setUrl()},
onPageFinished: (src) => debugPrint('The page has finished loading: $src\n'),
jsContent: const {
EmbeddedJsContent(
js: "function testPlatformIndependentMethod() { console.log('Hi from JS') }",
),
EmbeddedJsContent(
webJs: "function testPlatformSpecificMethod(msg) { TestDartCallback('Web callback says: ' + msg) }",
mobileJs:
"function testPlatformSpecificMethod(msg) { TestDartCallback.postMessage('Mobile callback says: ' + msg) }",
),
},
webSpecificParams: const WebSpecificParams(
printDebugInfo: true,
),
mobileSpecificParams: const MobileSpecificParams(
androidEnableHybridComposition: true,
),
navigationDelegate: (navigation) {
debugPrint(navigation.content.sourceType.toString());
return NavigationDecision.navigate;
},
);
}
void _setUrl() {
webviewController.loadContent(widget.Url, SourceType.urlBypass, headers: {'Cmdbuild-authorization': widget.Token});
}
Widget buildSpace({
Axis direction = Axis.horizontal,
double amount = 0.2,
bool flex = true,
}) {
return flex
? Flexible(
child: FractionallySizedBox(
widthFactor: direction == Axis.horizontal ? amount : null,
heightFactor: direction == Axis.vertical ? amount : null,
),
)
: SizedBox(
width: direction == Axis.horizontal ? amount : null,
height: direction == Axis.vertical ? amount : null,
);
}
}
I'm trying to build a sort function in order to sort JSON data.
For this, I have a button that opens a "showModalBottomSheet".
Within it I can choose the following data of the school class numbers.
So in my data I have 6 classrooms when loading in my constructor.
My filter is represented by buttons which are active or not if the filter contains the number of the classroom. My code works pretty much, my problem is that when I select a filter button in order to activate or not the filter, the button is deleted instead of staying but changing color
My notifier :
class TablesNotifier with ChangeNotifier {
// Services
// ---------------------------------------------------------------------------
final jsonSelectorService = locator<JsonSelectorService>();
// Variables
// ---------------------------------------------------------------------------
//all data from my classerooms in JSON
List<ClassroomModel> classrooms;
// Data that I will display and reconstruct based on my filter parameters
List<ClassroomModel> classroomsFiltered;
List<int> numberOfClassrooms = List();
// Model which will store the parameters of my filters and as a function I will load the data to display
FilterClassroomsModel filterClassroomsModel = FilterClassroomsModel();
// Constructor
// ---------------------------------------------------------------------------
TablesNotifier(){
_initialise();
}
// Initialisation
// ---------------------------------------------------------------------------
Future _initialise() async{
classrooms = await jsonSelectorService.classrooms('data');
classroomsFiltered = classrooms ;
// I install the number of existing classrooms
// Here the result is [1,2,3,4,5,6]
classrooms.forEach((element) {
if(!numberOfClassrooms.contains(element.type)){
numberOfClassrooms.add(element.type);
}
});
// I install the number of classrooms activated by default in my filter
// As I decide to display all my classrooms by default
// My filter on the classrooms must contain all the loaded classrooms
filterClassroomsModel.classrooms = numberOfClassrooms;
notifyListeners();
}
// Functions public
// ---------------------------------------------------------------------------
void saveClassroomsSelected(int index)
{
// Here my classroom model also contains the numbers of the classrooms that I want to filter
if(filterClassroomsModel.classrooms.contains(index)){
filterClassroomsModel.classrooms.remove(index);
}else{
filterClassroomsModel.classrooms.add(index);
}
notifyListeners();
}
}
I have identified that in my function initialize () if I change my code by this it works :
filterClassroomsModel.classrooms= numberOfClassrooms; // this
filterClassroomsModel.classrooms= [1,2,3,4,5,6]; // By this
I am losing the dynamic side of my classroom calculation and that does not suit me. But I don't understand this behavior.
My view :
class TableScreen extends StatelessWidget {
final String title;
TableScreen({Key key, #required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: MenuDrawerComponent.builder(context),
appBar: AppBar(
backgroundColor: AppColors.backgroundDark,
elevation: 0,
centerTitle: true,
title: Text(title),
),
floatingActionButton: FloatingActionButton.extended(
icon: Icon(Icons.sort),
label: Text('Filter'),
onPressed: () async{
slideSheet(context);
},
backgroundColor: AppColors.contrastPrimary,
),
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context)
{
var _tableProvider = Provider.of<TablesNotifier>(context);
if(_tableProvider.chargesFiltered == null){
return Center(
child: CircularProgressIndicator(
backgroundColor: AppColors.colorShadowLight,
),
);
}else{
return Column(
children: <Widget>[
Expanded(
child: Container(
padding: EdgeInsets.only(top: 10, right : 20, left : 20),
child: ListView.builder(
itemCount: _tableProvider.classroomsFiltered.length,
itemBuilder: (context, index){
return Container(
child: Column(
children: [
Row(
// Some classrooms data
),
],
),
);
},
),
)
),
],
);
}
}
void slideSheet(BuildContext context) {
var _tableProvider = Provider.of<TablesNotifier>(context, listen:false);
showModalBottomSheet(
context: context,
isScrollControlled: true,
isDismissible: true,
builder: (context) {
return Wrap(
children: [
Container(
color: Color(0xFF737373),
child: Container(
child: Column(
children: <Widget>[
// Some filters ...
// Here I want to rebuild the list of button for show the changes
ChangeNotifierProvider.value(
value: _tableProvider,
child: Consumer<TablesNotifier>(
builder: (context, model, child){
return _listOfClassrooms(context);
}
),
),
],
),
),
),
]
);
});
}
Widget _listOfClassrooms(BuildContext context){
var _tableProvider = Provider.of<TablesNotifier>(context);
List<Widget> list = List<Widget>();
var listClassrooms = _tableProvider.numberOfClassrooms;
var filterClassrooms = _tableProvider.filterClassroomsModel.classrooms;
for (var i = 0; i < listClassrooms.length; i++) {
int selectIndex = 0;
if(filterClassrooms.contains(listClassrooms[i])){
selectIndex = listClassrooms[i];
}
list.add(
RadioComponent(
text: "${listClassrooms[i]}",
index: listClassrooms[i],
width: (MediaQuery.of(context).size.width - 56) /3,
selectedIndex: selectIndex,
onPressed: _tableProvider.saveChargesSelected,
),
);
}
return Wrap(
spacing: 8.0, // gap between adjacent chips
runSpacing: 8.0, // gap between lines
children: list
);
}
}
My FilterClassroomsModel :
class FilterClassroomsModel {
int order;
int sort;
List<int> classrooms;
FilterClassroomsModel ({
this.order = 0,
this.sort = 0,
this.classrooms = const[],
});
#override
String toString() {
return '{ '
'${this.order}, '
'${this.sort}, '
'${this.classrooms}, '
'}';
}
}
EDIT : Resolved topic. Thanks to Javachipper.
In the notifier I replace that :
filterClassroomsModel.classrooms = numberOfClassrooms;
By that :
filter.classrooms = List<int>();
filter.classrooms.addAll(numberOfClassrooms);
change this:
filterClassroomsModel.classrooms = numberOfClassrooms;
to:
filterClassroomsModel.classrooms.addAll(numberOfClassrooms);
Update (you can also do it like this):
filterClassroomsModel.classrooms= new List<int>();
filterClassroomsModel.classrooms.addAll(numberOfClassrooms);
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
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