I created a model class for Provider. Which will fit the function of getting data from SharedPreferences
Future getDataPerson() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
id = prefs.getInt('id') ?? 000;
name = prefs.getString('name') ?? "Фамилия Имя Отчество";
phone = prefs.getString('phone') ?? "3231313";
email = prefs.getString('email') ?? "";
accessToken = prefs.getString('accessToken') ?? "нет токенааа";
_login = prefs.getString('login') ?? "нет логинааа";
_password = prefs.getString('password') ?? "нет пароляяя";
notifyListeners();
}
That's how I implement my Provider
body: MultiProvider(
providers: [
ChangeNotifierProvider<ApiClient>(create: (_)=>ApiClient()),
ChangeNotifierProvider<FinalModel>(create: (_)=>FinalModel()),
],
child: FinalScreenState(),
),
In initState, I call this function.
#override
void initState() {
super.initState();
finalModel = Provider.of<FinalModel>(context,listen: false);
getDataPerson();
}
Future getDataPerson() async{
return await finalModel.getDataPerson();
}
And in the code I get these variables and paste them into Text
idController.text = finalModel.getId.toString();
return Padding(padding: EdgeInsets.only(top: 15, bottom: 10,right: 1,left: 1),
child:Center(
child: TextField(
controller: idController,
))
);
However, only the values that I wrote in the code are inserted into the text. In this line it is "3231313"
prefs.getString('phone') ?? "3231313";
I tried calling the get Data Person function in different places in the code. In the build method itself.
The data I want to insert I take from json immediately after the user logs in using the button and receives a response from the api
var statusOne =await apiClient.signIn(_loginEmail1.text, _passwordParol1.text);
Map<String, dynamic> map = jsonDecode(rawJson);
bool status = map['status'];
if (status == true) {
//Entry in SharedPreferences
setDataPerson();
//The screen on which the data is displayed
Navigator.pushReplacementNamed(context, 'final');}
method setDataPerson()
void setDataPerson() async {
final prefs = await SharedPreferences.getInstance();
var statusOne =
await apiClient.signIn(_loginEmail1.text, _passwordParol1.text);
var rawJson = AuthModel.fromJson(statusOne.data);
await prefs.setInt('id', rawJson.data!.performerId!);
await prefs.setString('name', rawJson.data!.fullName!);
await prefs.setString('phone', rawJson.data!.phone!);
await prefs.setString('accessToken', rawJson.data!.accessToken!);
}
Build method
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
IdWidget(),
NameWidget(),
PhoneWidget(),
EmailWidget(),
],
);
}
PhoneWidget
class PhoneWidget extends StatelessWidget {
Widget build(BuildContext context) {
var finalModel = Provider.of<FinalModel>(context,listen: true);
return Padding(padding: EdgeInsets.only(top: 10, bottom: 10,right:
1,left: 1),
child: Consumer<FinalModel>(
builder: (context, model, widget) {
finalModel.phoneController.text = model.phone;
return Center(
child: TextField(
controller: finalModel.phoneController,
enabled: false,
decoration: const InputDecoration(
labelText: "Телефон",
contentPadding: EdgeInsets.only(left: 10, top: 10, bottom: 10),
border: OutlineInputBorder(),
),
));
})
);
}
}
However, even if you do not receive this data from the network and just write a regular string, the data will not have time to be displayed on the screen.
It's worth noting here that when I restart the screen. And that is, I update the initState and build method. Then the data is updated and immediately displayed on the screen
I am not considering inserting the listen:true parameter into provider, , because the getData Person function will be called too often.
Wrap Consumer widget to your Columnwidget so registered listeners will be called.
Widget IdWidget() {
return Consumer<YourModelClass>(
builder: (context, model, widget) => Padding(
padding: EdgeInsets.only(top: 15, bottom: 10, right: 1, left: 1),
child: Center(
child: TextField(
controller: model.id,
))));
}
Related
I'm using Firebase Realtime database to add chat features to my app. In the database I have the following data:
{
events: {
"event-uuid1": {
"chat-uuid1": {
"message": "hey"
},
"chat-uuid2": {
"message": "hey again"
}
}
}
}
In my Flutter app, I have this StreamBuilder (I know this is lengthy, I'm not sure where the problem is so providing more rather than less):
class _EventChatScreenState extends ConsumerState<EventChatScreen> {
FirebaseDatabase dbInstance = FirebaseDatabase.instance;
late TextEditingController _messageFieldController;
late DatabaseReference eventDbRef;
#override
void initState() {
super.initState();
_messageFieldController = TextEditingController();
eventDbRef = dbInstance.ref("none");
}
#override
void dispose() {
_messageFieldController.dispose();
super.dispose();
}
Map<String, ChatMessage> chatMessages = {};
#override
Widget build(BuildContext context) {
final user = ref.watch(userProvider);
final event = ModalRoute.of(context)!.settings.arguments as EventRepository;
if (eventDbRef.path == "none") {
print("IT IS NONE");
eventDbRef = dbInstance.ref("/events/${event.event.eventId}/");
print(eventDbRef.path); // Print's correct value
}
return StreamBuilder(
stream: eventDbRef.onChildAdded,
builder: (context, snapshot) {
if (chatMessages == {}) {
return const Text("Loading...");
}
DatabaseEvent data;
if (snapshot.hasData) {
data = snapshot.data as DatabaseEvent;
ChatMessage newChatMessage = ChatMessage(
chatMessageId: "",
userId: "",
displayname: "",
message: "",
datetime: "",
);
for (var child in data.snapshot.children) {
switch (child.key) {
case "chatMessageId":
newChatMessage.chatMessageId = child.value.toString();
break;
case "userId":
newChatMessage.userId = child.value.toString();
break;
case "displayName":
newChatMessage.displayname = child.value.toString();
break;
case "message":
newChatMessage.message = child.value.toString();
break;
case "datetime":
final datetime = DateTime.parse(child.value.toString());
final DateFormat formatter = DateFormat('h:mm aa');
final String formatted = formatter.format(datetime);
newChatMessage.datetime = formatted;
break;
default:
}
}
if (chatMessages[data.snapshot.key] == null) {
chatMessages[data.snapshot.key!] = newChatMessage;
}
}
return ListView.builder(
itemCount: chatMessages.length,
itemBuilder: (context, index) {
String key = chatMessages.keys.elementAt(index);
if (chatMessages[key]!.userId == user.user.userId) {
return UnconstrainedBox(
alignment: Alignment.centerRight,
child: Container(
margin: const EdgeInsets.symmetric(vertical: 5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.only(left: 10),
child: Text(chatMessages[key]!.displayname),
),
Container(
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
color: Theme.of(context).colorScheme.primary,
),
child: Text(chatMessages[key]!.message,
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
Container(
padding: const EdgeInsets.only(left: 10),
child: Text(chatMessages[key]!.datetime),
),
],
),
),
);
}
},
);
},
),
The problem is that when the user goes to the chat screen one of the messages will already be present in the chat. I would expect there to be nothing since I am not setting any initial data anywhere, not using Realtime Database's persistence, and not using my own local database yet.
My understanding of StreamBuilders is that they only get new data as it comes in, not data that may already exist and is thus not sent through it (Ie. when a new chat message is sent the stream should receive it, which works, but it should not receive chat message messages already in the database). If that understanding is wrong then why am I only getting one message despite there being 2, 3, 4, etc. in the database?
Perhaps I'm understanding/using StreamBuilders, Firebase Realtime Database, or both incorrectly?
Maybe your understanding about streambuilder is wrong.
Lets say you use FutureBuilder, It'll wait till the future is over and then builds the widget accordingly but It'll not build again if something changes in your database, but for StreamBuilder, It'll basically listen (and get initial data from stream, here your db) to the stream and build whenever it changes or a new data is added to stream (here database) it will get the updated data and build the widget again.
Read here:
https://firebase.flutter.dev/docs/firestore/usage/#realtime-changes
Assign this eventDbRef.onChildAdded to a variable in initState and then use the variable as your stream parameter in streambuilder. Having the db call in Streambuilder causes it to be rerun everytime the widget tree builds.
I have a textAheadField and successfully get data from it, i call setState so the data can be saved locally in statefullwidget. and i want to store it in database firestore but inside the update method firestore the variable that i want (imageuniversitycollege) is empty and has not been update like in the setstate should be.
This is the textAheadField
String imageuniversitycollege = "";
Widget CollegeBuildTextAhead() {
return Container(
margin: EdgeInsets.symmetric(horizontal: 20, vertical: 8),
child: TypeAheadField<SchoolData?>(
hideSuggestionsOnKeyboardHide: true,
debounceDuration: Duration(milliseconds: 500),
textFieldConfiguration: TextFieldConfiguration(
controller: collegeAheadController,
decoration: InputDecoration(
prefixIcon: Icon(Icons.school),
border: OutlineInputBorder(),
hintText: 'Search your school',
),
),
suggestionsCallback: SchoolApi.getUserSuggestions,
itemBuilder: (context, SchoolData? suggestion) {
final datasugestion = suggestion!;
return ListTile(
leading: Container(
width: 40,
height: 40,
child: Image.network(
datasugestion.image,
fit: BoxFit.cover,
),
),
// leading for image
title: Text(datasugestion.university),
);
},
onSuggestionSelected: (SchoolData? choose) {
final datasugestion = choose!;
collegeAheadController.text = datasugestion.university; //this works fine in the update
final String imageuniversitycollege = datasugestion.image;
setState(() {
final String imageuniversitycollege = datasugestion.image;// this is the data i want
print(imageuniversitycollege); //i print it in here its get the usual link of image
});
},
),
);
}
The usual elevated button
Center(
child: ElevatedButton(
child: const Text(
"Confirm",
),
onPressed: () {
updateEducationCollege();
},
),
)
this is the method update, it works but the image is not filled
Future updateEducationCollege() async {
try {
print(FirebaseAuth.instance.currentUser?.uid);
await FirebaseFirestore.instance
.collection("education")
.doc(FirebaseAuth.instance.currentUser!.uid)
.set({
"uid": FirebaseAuth.instance.currentUser?.uid,
"College": collegeAheadController.text,
"imageCollege": imageuniversitycollege,
}).then((value) => print("Data changed successfully"));
} on FirebaseException catch (e) {
Utils.showSnackBar(e.message);
}
}
The collegeAheadController.text seems fine still successfully retrieve the data like the image bellow
what should i do? to get this data??
Just change
setState(() {
final String imageuniversitycollege = datasugestion.image;
});
to
setState(() {
imageuniversitycollege = datasugestion.image;
});
Instead of updating the existing state variable, you are creating a new local variable. Thats the issue.
Happy coding:)
When you try update your variable you are define new one, change your onSuggestionSelected to this:
onSuggestionSelected: (SchoolData? choose) {
final datasugestion = choose!;
collegeAheadController.text = datasugestion.university;
final String imageuniversitycollege = datasugestion.image;
setState(() {
imageuniversitycollege = datasugestion.image; //<-- change this
print(imageuniversitycollege);
});
},
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
While working with Flutter for a new application client for Kanboard, I encountered the following problem. I have a FutureBuilder that should return a select dropdown menu with items but, for some reason, the Snapshot data is null, although the Future method does resolves and has data on return.
Full page.dart code here: https://pastebin.com/J48nxsdZ
The block having the problem is the following:
Widget _columnSelect() {
return FutureBuilder(
future: columnProvider.getColumns(task.projectId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<DropdownMenuItem<String>> columnList = [];
if (snapshot.hasData) {
columnList.add(DropdownMenuItem<String>(
child: Text('Select Column'), value: 0.toString()));
_columns = snapshot.data;
} else {
columnList.add(DropdownMenuItem<String>(
child: Text('Loading..'), value: 0.toString()));
}
_columns.forEach((column) {
columnList.add(DropdownMenuItem<String>(
child: Container(
child: Text(
column.title,
),
),
value: column.id.toString()));
});
return Container(
// margin: EdgeInsets.only(left: 40.0),
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: DropdownButtonFormField(
icon: Padding(
padding: const EdgeInsets.only(right: 12),
child: Icon(Icons.view_column, color: Colors.blue),
),
items: columnList,
value: _columnId,
decoration: InputDecoration(helperText: 'Optional'),
onChanged: (newValue) {
_columnId = newValue;
},
),
);
},
);
}
This is a duplicate of a widget in the same form for a user dropdown select. The original widget (in the same page) is the following:
Widget _ownerSelect() {
return FutureBuilder(
future: userProvider.getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<DropdownMenuItem<String>> usernameList = [];
if (snapshot.hasData) {
usernameList.add(DropdownMenuItem<String>(
child: Text('Select Owner'), value: 0.toString()));
_users = snapshot.data;
} else {
usernameList.add(DropdownMenuItem<String>(
child: Text('Loading..'), value: 0.toString()));
}
_users.forEach((user) {
usernameList.add(DropdownMenuItem<String>(
child: Container(
child: Text(
user.name,
),
),
value: user.id.toString()));
});
return Container(
// margin: EdgeInsets.only(left: 40.0),
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: DropdownButtonFormField(
icon: Padding(
padding: const EdgeInsets.only(right: 12),
child: Icon(Icons.person, color: Colors.blue),
),
items: usernameList,
value: _ownerId,
decoration: InputDecoration(helperText: 'Optional'),
onChanged: (newValue) {
_ownerId = newValue;
},
),
);
},
);
}
For some reason, the "_columnSelect" AsyncSnapshot is null always, even when the Future method is working fine:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:kanboard/src/models/column_model.dart';
import 'package:kanboard/src/preferences/user_preferences.dart';
class ColumnProvider {
final _prefs = new UserPreferences();
Future<List<ColumnModel>> getColumns(projectId) async {
final Map<String, dynamic> parameters = {
"jsonrpc": "2.0",
"method": "getColumns",
"id": 887036325,
"params": {"project_id": projectId}
};
final credentials = "${_prefs.username}:${_prefs.password}";
Codec<String, String> stringToBase64 = utf8.fuse(base64);
String encoded = stringToBase64.encode(credentials);
final resp = await http.post(
Uri.parse(_prefs.endpoint),
headers: <String, String>{"Authorization": "Basic $encoded"},
body: json.encode(parameters),
);
final decodedData = json.decode(utf8.decode(resp.bodyBytes));
final List<ColumnModel> columns = [];
final List<dynamic> results = decodedData['result'];
if (decodedData == null) return [];
results.forEach((column) {
final columnTemp = ColumnModel.fromJson(column);
columns.add(columnTemp);
});
print(columns);
return columns;
}
}
The output of "print(columns)" returns:
I/flutter ( 9486): [Instance of 'ColumnModel', Instance of 'ColumnModel', Instance of 'ColumnModel', Instance of 'ColumnModel']
I don't know what I'm missing here. The form has 2 users dropdown select (with the original FutureBuilder Widget) which works just fine. The Column widget with the Future Builder is the one with the "null" problem in snapshot.data.
Thank you in advance for your time and support with this!
I just found where the problem was:
In the form page(new Task page), The columnProvider.getColumns(task.projectId)) wasn't executing because the "task.projectId" parameter is a String, but the API needs an int.
I was confused because the method were being called by the previous page (A project Page with all the tasks) and the getColumn's argument was indeed an integer: int.parse(projectId).
The Kanboard API doesn't return an error code if the ID parameter is other than INT with this specific call "getColumns" (for some reason).
Of course, Flutter (or Dart) is waiting for a response from http.post that would never arrive. When comparing the two calls from the two pages, I noticed the difference.
So, in conclusion, I specified the int data type argument in the getColumn definition in order to avoid any confusion:
Future<List<ColumnModel>> getColumns(int projectId) async {
Best Regards!
I am using Futurebuilder to show List issue is its running function 2 time or 3 times i dont know and showing data 2 times.
My function code
getCustomerList() async {
customerS = [];
print('check running');
final storage = new FlutterSecureStorage();
String uUid = await storage.read(key: "uUid");
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('Transaction');
QuerySnapshot querySnapshot =
await _collectionRef.get();
// Get data from docs and convert map to List
List allData = querySnapshot.docs.where((element) => element['CustomerID'] == widget.data['customerID'])
.map((doc) => doc.data())
.toList();
print(allData);
print('allData length ${allData.length}' );
for (int i = 0; i < allData.length; i++) {
print(allData[i]);
customerS.add(allData[i]);
}
print(customerS);
print('cus length ${customerS.length}' );
return customerS;
}
My future builder code
FutureBuilder(
future: getCustomerList(),
builder: (context, snapshot) {
print('snapshot length ${snapshot.data.length}');
print(snapshot);
print(snapshot.data);
if (snapshot.hasData)
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
print('List length ${snapshot.data.length}');
return Padding(
padding: const EdgeInsets.only(left: 13, right: 13),
child: Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: Colors.grey, width: .5)),
),
child: Padding(
padding: const EdgeInsets.all(13.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'10th May ',
style: TextStyle(fontFamily: 'PoppinsMedium'),
),
Text(
snapshot.data[index]['give'].toString(),
style: TextStyle(
fontFamily: 'PoppinsMedium',
color: Colors.green),
),
],
),
),
),
);
},
);
else
return Text('Result: ${snapshot.data.body}');
}
),
Issue is i have only 2 arrays in list but its showing for look like its running two time i am also try to clear the array but nothing work.
You can look on this picture
enter image description here
I have only 2 array which are in red circle and its showing double can see in blue circle.
Why you are using customerS I mean its saving data in this by initState and when Future call its double.
Remove it from initState and just simply return allData like this
getCustomerList() async {
customerS.clear();
print('check running');
final storage = new FlutterSecureStorage();
String uUid = await storage.read(key: "uUid");
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('Transaction');
QuerySnapshot querySnapshot = await _collectionRef.get();
// Get data from docs and convert map to List
List allData = querySnapshot.docs
.where((element) => element['CustomerID'] == widget.data['customerID'])
.map((doc) => doc.data())
.toList();
print(allData);
return allData;
}
From the docs:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
This line:
FutureBuilder(
future: getCustomerList(), <- this line
...
)