Getting a specific String from List.generate with onTap - wordpress

I am using WordPress REST API to fetch posts from a WordPress site with this piece of code with the help of wordpress_api package.
class ProvideTitle with ChangeNotifier {
List<String> Titles = [];
List<String> Descriptions = [];
List<String> Urls = [];
void ClearTitle() { //clears lists when called
Titles.clear();
Descriptions.clear();
Urls.clear();
}
void TitleFetcher(term) async { //takes term from query and adds parameters
final String searchTerm=term;
final api = WordPressAPI('https://wordpresswebsite.com');
WPResponse res = await api.posts.fetch(args: {
"search" : searchTerm,
"per_page" : "20",
"page" : "1"
});
for (final post in res.data){ //returns String to fill Lists below for each post found
Titles.add(post.title);
Descriptions.add(post.content);
Urls.add(post.link);
}
notifyListeners();
}
}
Everything is working as expected and class notifies all listeners with each List<//Strings> respectively.
Problem here is I build a list with List.generate using List<//String> Title in search bar recommendations with material_floating_search_bar, code looks like this.
class SearchPage extends StatefulWidget {
const SearchPage({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => SearchPageState();
}
class SearchPageState extends State<SearchPage> {//search bar from package mentioned
late String resultValue= "";
final controller = FloatingSearchBarController(); //controller for search bar
#override
Widget build(BuildContext context) {
return SafeArea(
child: Consumer<ProvideTitle>(builder: (context, provideTitle, child) {//Provider used for listening to ProvideTitle class
return FloatingSearchBar(
debounceDelay: const Duration(milliseconds: 165),
onQueryChanged: (query) async { //registers keyboard input
query != "" ? provideTitle.TitleFetcher(query) //sends input to TitleFetcher function inside ProvideTitle class
: provideTitle.ClearTitle(); //clears lists to stop showing fetced data with empty query(please feel free to recommend me a better way to do)
},
controller: controller,
hint: "Search",
backdropColor: Colors.transparent,
borderRadius: BorderRadius.circular(24),
physics: const BouncingScrollPhysics(),
builder: (context, _) => BuildBody(), //calls list builder as recommendations
body: resultValue != "" ? SearchResults(): //result body going to be called when some entry chosen from list
SizedBox());//page stays empty when there is no action
}),
);
}
Widget BuildBody() {
ProvideTitle model = Provider.of<ProvideTitle>(context); //ProvideTitle called for its lists as model
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
child: Column(
children:
List.generate(model.Titles.length, (index) => model.Titles[index].toString()) //list generated as long as Title[index] which is 20 max because we only call 20 post from api
.map((e) => ListTile(
onTap: () { // i want this onTap to register which tile I chosed
model.ClearTitle();//clears lists for next search
controller.close();//closes search bar recommendations
},
title: Text(e),
))
.toList(),
),
),
);
}
}
After that list generated based on search, I want to chose one of the tiles to build a widget that contains fetched content of that post.
I created a class for contents to be displayed based on the tile we've chosen earlier but everything is also okay with that class.
How can I effectively call my class to show tiles contents that I chosen earlier, preferably without further fetching to stop wasting server resources and my devices resources.
I am completely new at flutter and developing this app for not more than three weeks without any previous coding experience other than few basic java apps that works on console, so please please please feel free to correct me in any way rather it's about my problem or not. Much thanks in advance.
Also this is my first question on here so excuse if formatting is not adequate enough for site.

Using indexOf() successfully returns the int value of chosen tile.
List.generate(model.titles.length,
(index) => model.titles[index].toString())
.map((e) => ListTile(
onTap: () {
setState(() {});
controller.close();
model.resultValue = model.titles.indexOf(e); // we are returning the int value of chosen tile to another class here
model.parsData();
model.clearFetchedData();
},
title: Text(e),
))
.toList(),

Related

How can I mix a statenotifier, with streamproviders that fetch 2 collections from Firebase to get constant update on a chat list

This is my first question here and I hope I’m not making it too complex.
So, I’m a junior programmer and I’ve start learning flutter, firebase and riverpod a couple of months ago and I’m stuck in a specific project.
For part of the app, what I need to do is something very similar to WhatsApp:
A screen with all the user chats,
This screen (or part of it) should update every time a chat gets a new message, showing the last message snippet and turning into bold if not read,
The shown chats should change between unarchived and archived, depending on a button in the UI.
Couldn’t have picked up an easier starting project, right? ;) (now every time I look at WhatsApp I say wow!)
Regarding firebase/firestore I’m fetching 2 different collections for this:
the sub-collection ‘chats’ within the ‘user_chats’ collection: where I get all the chat Ids plus it’s status (if this chat is archived and if the last message was read), for the current user,
the main ‘chats’ collection, where I have the main info of each chat.
At this moment I’m doing this:
In the chats_screen (UI) I’m fetching my chat provider: userChatsProvider
(note: this is just an example of the UI implementation. I have another implementation for it, but as long I get the chatName and lastMsgContent updated for each chat, perfect.)
class ChatsScreen extends ConsumerWidget {
const ChatsScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
(…)
return Scaffold(
appBar: (…) // not relevant for this question
body: Center(
child: Consumer(
builder: (BuildContext context, WidgetRef ref, Widget? child) {
return ref.watch(userChatsProvider).when(
loading: () => const CircularProgressIndicator(),
error: (err, st) => Center(child: Text(err.toString())),
data: (chatData) {
return Column(
children: [
// button to get archived chats
child: TextButton(
child: (…)
onPressed: () {}),
),
Expanded(
child: ListView.builder(
itemCount: chatData.length,
itemBuilder: (ctx, index) => Row(
children: [
Text (chatData[index].chatName!),
Text (chatData[index].lastMsgContent!),
]
),
),
),
]
);
}
);
}
)
)
);
}
}
In the chats_provider (provider) I’m fetching the 2 repository providers and joining them into a specific model I’ve created for this screen:
final userChatsProvider = FutureProvider.autoDispose<List<ChatScreenModel>>((ref) async {
const String userId = ‘XXX’; // this will be substituted by a dynamic variable with the current user Id
try {
final List<UserChat> userChats =
await ref.watch(userChatsRepositoryProvider).get(userId: userId);
final List<String> chatIdList = userChats.map<String>((e) => e.id).toList();
final List<Chat> chats =
await ref.watch(chatsRepositoryProvider).get(chatIds: chatIdList);
// ref.maintainState = true;
return toChatScreenModel(chats, userChats);
} on Exception catch (e) {
throw const CustomException();
}
});
In the repositories I’m fetching the firestorm collections I mentioned above. Here’s an example:
final userChatsRepositoryProvider =
Provider<UserChatsRepository>((ref) => UserChatsRepository(ref.read));
class UserChatsRepository {
final Reader _read;
const UserChatsRepository(this._read);
Future<List<UserChat>> get({required String userId}) async {
try {
final snap = await _read(firebaseFirestoreProvider)
.collection('users_chats/$userId/chats')
.get();
// Maybe this step is not necessary, but I’ve decided to transform the data into temporary models, before sending to provider
List<UserChat> userChats =
snap.docs.map((doc) => UserChat.fromJson(doc.data(), doc.id)).toList();
return userChats;
} on FirebaseException catch (e) {
throw CustomException(message: e.message);
}
}
}
And by the way, this is the model I’m sending to the UI:
class ChatScreenModel {
final String? id;
final String? chatName;
final String? chatImage;
final String? lastMsgContent;
final String? lastMsgDate;
final ChatType? chatType;
final bool? archived;
final bool? msgRead;
ChatScreenModel({
this.id,
this.chatName,
this.chatImage,
this.lastMsgContent,
this.lastMsgDate,
this.chatType,
this.archived,
this.msgRead,
});
Problems with this implementation:
I’m getting the user chats in the screen, but they don’t update since I’m not using a stream. So I get a snapshot, but it will only update if I leave and enter that chats_screen again. And it would be important to have it updating with a stream.
I’m showing all the chats, and not a filtered list with only the unarchived chats.
Also, related with the previous point, I still don’t have the archived button working, to only show the archived chats.
I’ve lost many, many hours trying to understand how I could implement a stream provider and a state notifier provider in this workflow.
Tried many combinations, but without success.
Can anyone help me understand how to do this?
Priority: transform these providers into stream providers (so it updates the UI constantly).
Nice to have: also include the archived/unarchived dynamic to filter the chats that appear and be able to switch between them.
Thanks a lot. :)

How do I print a list from a Firebase Database query using Flutter?

I'm trying to print a list of attributes from my firebase database. The database is currently structured like this:
I would first like to print a list of show names to the console so I can see that it works and then add it to a ListView later. Any help is appreciated!
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// This is the model class
class Mod {
final String name;
final String nextEpisode;
final String prevEpisode;
Mod(this.name, this.nextEpisode, this.prevEpisode);
Mod.fromJson(Map<String, dynamic> json)
: name = json['name'],
nextEpisode = json['nextEpisode'],
prevEpisode = json['prevEpisode'];
}
// This is the screen class
class FTest2 extends StatefulWidget {
#override
_FTest2State createState() => _FTest2State();
}
class _FTest2State extends State<FTest2> {
List<Mod> list = List();
MakeCall() {
final mainReference = FirebaseDatabase.instance.reference();
mainReference.child('-M5Uol7Xldnc8wvNXnNg').once().then((DataSnapshot dataSnapshot){
this.setState(() {
for(var value in dataSnapshot.value){
list.add(Mod.fromJson(value));
}
});
});
print(list);
}
void getData() {
MakeCall();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('This is App Bar for the FB Test')),
body: Column(
children: <Widget>[
RaisedButton(
child: Text('Press for data'),
onPressed: () {
getData();
},
),
],
));
}
}
You're looping over the nodes of one specific show, which means that your value is actually one of the child properties under that: name, nextEpisode, prevEpisode. What you're probably looking for is to listen to onChildAdded for all shows, and then get the name property for each:
mainReference.child('shows')
.onChildAdded
.forEach((event) => {
print(event.snapshot.key+": "+event.snapshot.value.toString());
this.setState(() {
list.add(Mod.fromJson(event.snapshot.value["name"]));
});
});
Also see my answer form a few weeks ago here: Flutter: Firebase Real-Time database orderByChild has no impact on query result
Your reference is wrong, you need to traverse the database from top to the node you want to retrieve, therefore use the following:
final mainReference = FirebaseDatabase.instance.reference("shows");
mainReference.child('-M5Uol7Xldnc8wvNXnNg').once().then((DataSnapshot dataSnapshot){
pass the argument shows to the reference() method.

Cannot communicate with global variables in async functions in Flutter

I'm working in Flutter and I'm having a problem with communicating with the rest of my program from inside an asynchronous function. In specific I'm using an http request (which HAS to be an asynchronous function, so that's not an option to do differently) and trying to (after the async has completed) SET a previously instantiated global variable equal to the part of the response from the http request.
In order to try and parse down the issue, I created a minimal-code example from the basic flutter project.
Code Example to follow.
String username = 'xxx';
String password = 'xxx';
String basicAuth = 'Basic ' + base64Encode(utf8.encode('$username:$password'));
String url = 'xxx';
String user_phone = '5555555555';
String locationId1 = 'xxx';
String locationId2 = 'xxx';
Map info;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Session ID:',
),
Text(
'$sessionID',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: createSessionID,
tooltip: 'Create Session ID',
child: Icon(Icons.add),
),
);
}
}
Future<void> createSessionID() async {
http.Response response = await http.post(
url,
headers: <String, String>{'authorization': basicAuth},
body: {'user_phone': user_phone, 'locationId1': locationId1, 'locationId2': locationId2},
);
info = jsonDecode(response.body);
sessionID = info['session_id'];
print (info['session_id']);
return info;
}
Effectively the issue is that I can PRINT the values to the debug console inside the async function, BUT if I try to set that global Map variable to anything, it is constantly null, and never set.
I've tried setting it as a Future and returning the variable, but that doesn't change the fact that it's still not being set properly, as it returns a null for some unknown reason.
So in the above example if I change it to a:
Future<Map> createTrip() async {
<Same Code, but with
Map data = jsonDecode(response.body);
return data;
>
}
<void> updateInfo() async {
info = await createSessionID();
}
And then print the values inside info, it still tells me the same thing, that info itself is null. Ergo it's never being set from inside the asynchronous function. And I just don't understand why.
Thoughts? Ideas?
(Edited Twice to add original code, and clear up confusion)
So the issue here, was that I didn't properly understand the concept of a Future. (And honestly, I probably still don't, lol).
So I thought that if you would await a response, it would automatically change a Future of a variable-type into a variable of the type it was supposed to be.
That... Doesn't happen.
So instead, you need to communicate with the different parts of your program in a different way. The solution I eventually used (Likely not the best, but it works, so shrug good enough for me) was to use Global variables to store the information, then use getters to set the local variable my program needs equal the global variable that the server-call was used to set.
So an example of this:
Map data; // Set globally, outside the function;
Future<void> createTrip() {
// Code for server call
data = jsonDecode(response.body);
return;
}
Map getData () {
return data;
}
Thus, the issue of trying to return a future, and decode it is avoided entirely. Again, this is likely highly inefficient, I assume there are better ways to do it... But at least it works, lol.
I'm pretty sure you have everything correct here, you're just not telling Flutter to update the widget now that you've updated the value.
Make sure you call setState with your new assignments.
Future<void> createSessionID() async {
http.Response response = await http.post(
url,
headers: <String, String>{'authorization': basicAuth},
body: {'user_phone': user_phone, 'locationId1': locationId1, 'locationId2': locationId2},
);
info = jsonDecode(response.body);
setState(() => sessionID = info['session_id']); // <--------new code
print (info['session_id']);
return info;
}

Flutter: Stream has already been listened to [duplicate]

This question already has answers here:
Flutter : Bad state: Stream has already been listened to
(20 answers)
Closed 1 year ago.
I'm using BLoC to load my Preset Objects from Firestore. This is my Bloc Model:
class StatisticsBloc extends BlocBase {
List<Preset> _presets;
StreamController<List<Preset>> _presetsController = new StreamController();
Stream<List<Preset>> get getPresets => _presetsController.stream.asBroadcastStream();
StatisticsBloc() {
print('init Statistics Bloc');
_presets = [];
Firestore.instance.collection('Presets').snapshots().asBroadcastStream().listen(_onPresetsLoaded);
}
#override
void dispose() {
print('Disposed Statistics Bloc');
_presetsController.close();
}
void _onPresetsLoaded(QuerySnapshot data) {
_presets = [];
data.documents.forEach((DocumentSnapshot snap) {
Preset preset = Preset.fromDoc(snap);
_presets.add(preset);
});
_presetsController.sink.add(_presets);
}
}
Then I display the List like this:
class StatisticsPage extends StatelessWidget {
StatisticsPage() {
print('Created StatisticsPage');
}
#override
Widget build(BuildContext context) {
final StatisticsBloc statisticsBloc = BlocProvider.of<StatisticsBloc>(context);
final List<Preset> _ = [];
print(statisticsBloc.getPresets.isBroadcast);
return Scaffold(
appBar: AppBar(
title: Text('Statistics'),
),
body: StreamBuilder(
stream: statisticsBloc.getPresets,
initialData: _,
builder: (BuildContext context, AsyncSnapshot<List<Preset>> snapshot) {
if (snapshot.hasData) {
return ListView(
children: snapshot.data.map((Preset preset) {
print(preset.name);
return new ListTile(
title: new Text(preset.name),
subtitle: new Text(preset.id),
);
}).toList(),
);
} else {
Text('No Data');
print('No Data');
}
}
)
);
}
}
The problem is, I show the the StatisticsPage in a Tabbar, so it will be build muliple times when I switch tabs and go back to it. On the first visit it works but when I switch tabs and go back to it, the widget get rebuild and I get the error: Bad state: Stream has already been listened to.. I tried to declare the getPresets Stream as a BroadcastStream as you can see in StatisitcsBloc but that doesn't work.
Also as a secoundary question: Is there a better way to transform Stream<QuerySnapshot> that I get from Firestore to Stream<List<Presets>>?
It is easy, take a look to BehaviorSubject class from RxDart library.
BehaviorSubject is, by default, a broadcast (aka hot) controller, in order to fulfill the Rx Subject contract. This means the Subject's stream can be listened to multiple times.
So, just change line
StreamController<List<Preset>> _presetsController = new StreamController();
to
StreamController<List<Preset>> _presetsController = new BehaviorSubject();
and delete all
.asBroadcastStream()
That's it!
In official documentation it is not recommended to use asBroadcastStream()
A more dangerous way of creating a stream controller is to view a single-subscription controller through asBroadcastStream(). Invoking asBroadcastStream basically tells the single-subscription stream that the user wants to take over the lifetime management of the stream. In combination with cancelOnError subscribers, this can easily lead to single-stream subscriptions that are never closed and thus leak memory or resources.

Flutter/Dart and Firebase, How to get data in list not in realdatetime

I am creating a Flutter app that allows a list of products of a store. To practice, I have base myself in flutter firebase example:
I have managed to show a list with products stored in firebase, but my problem is that when a user adds a product, the list automatically refreshes, since it is realdatatime. The example code:
new Flexible(
child: new FirebaseAnimatedList(
key: new ValueKey<bool>(_anchorToBottom),
query: _messagesRef,
reverse: _anchorToBottom,
sort: _anchorToBottom
? (DataSnapshot a, DataSnapshot b) => b.key.compareTo(a.key)
: null,
itemBuilder: (BuildContext context, DataSnapshot snapshot,
Animation<double> animation, int index) {
return new SizeTransition(
sizeFactor: animation,
child: new Text("$index: ${snapshot.value.toString()}"),
);
},
),
),
In the code i use FirebaseAnimatedList() to load a query from firebase, it works perfectly, but when many products are added in a short time, for the user it would be very annoying.
I would like the user to refresh this manually.
I've seen that the firebase_list library exists, but I can not find any example of how to use it and I do not know if it would be the solution to the problem.
Thanks for any help or suggestions.
It looks like you can override didChangeDependencies and instead of calling setState when a child is added, note the change with a needsRefresh variable for example.
#override
void didChangeDependencies() {
if (widget.sort != null) {
_model = new FirebaseSortedList(
query: widget.query,
comparator: widget.sort,
onChildAdded: (_) => _needsRefresh = true, //_onChildAdded,
onChildRemoved: _onChildRemoved,
onChildChanged: _onChildChanged,
onValue: _onValue,
);
} else {
_model = new FirebaseList(
query: widget.query,
onChildAdded: _onChildAdded,
onChildRemoved: _onChildRemoved,
onChildChanged: _onChildChanged,
onChildMoved: _onChildMoved,
onValue: _onValue,
);
}
super.didChangeDependencies();
}

Resources