Flutter Algolia search multiple indices - flutter

Inside my Algolia application I have to indices which both contain users. Both of them must have a uid and a username (along other fields). I managed to implement the search for on of the indices. But how can I search in both indices?
I followed this tutorial but it does not say anything about searching multiple indices.
I found this about searching multiple indices but how can I do that in Flutter in combination with `Hits Searcher and infinite_scrolling_pagination.
This is my setup:
Stream<SearchMetadata> get _searchUsersMetadata =>
_userSearcher.responses.map(
SearchMetadata.fromResponse,
);
final PagingController<int, AlgoliaUser> _pagingController =
PagingController(firstPageKey: 0);
#override
void initState() {
super.initState();
_searchPage.listen((page) {
if (page.pageKey == 0) {
_pagingController.refresh();
}
_pagingController.appendPage(page.items, page.nextPageKey);
}).onError((error) => _pagingController.error = error);
_pagingController.addPageRequestListener(
(pageKey) => _userSearcher.applyState(
(state) => state.copyWith(page: pageKey),
),
);
}
final _userSearcher = HitsSearcher(
applicationID: 'myAppID',
apiKey: 'myApiKey',
indexName: 'users',
);
Stream<HitsPage> get _searchPage => _userSearcher.responses.map(
HitsPage.fromResponse,
);
_onSearchChanged(String query) {
if (_debounce?.isActive ?? false) {
_debounce?.cancel();
}
_debounce = Timer(const Duration(milliseconds: 500), () {
_userSearcher.applyState(
(state) => state.copyWith(
query: _searchTextEditingController.text,
page: 0,
),
);
});
}

Related

Force routing without context

am working on chat app , now am stuck on the idea of , if one of the two users left the page i wanna force the other user also to leave the page and back to home screen , i tried many things , boolean , conditions , will pop , nothing work , how can i implement a function to do this job , am using flutter , much appreciate .
i tried using boolean to set a condition to check if user exist , i tried to use will pop , i saw a couple of articles bout it , i even tried to set timer to force the second user to leave , but nothing happened , the problem is there's no action from the other user that make him leave the page , with or without on press , while the first user did the action and pressed the button to leave .
this is the button in the home page who do logic to match through firebase
child: ElevatedButton(
onPressed: () async {
var res = await FirebaseFirestore.instance
.doc(
'match/$_currentValue1$_currentValue2$_currentThumbValue')
.get();
var value = res.exists;
print('RESULT = $value');
/// If Code Is Not Exist
try {
if (value == false) {
/// Create Code In Match Document
await FirebaseFirestore.instance
.collection('match')
.doc('$_currentValue1$_currentValue2$_currentThumbValue')
.set({
'user1Id': FirebaseAuth.instance.currentUser!.uid,
'user1Name': userInfo['displayName'] ?? 'username',
'user2Id': '',
'user2Name': '',
});
print('Match Created By ${userInfo['displayName']}');
/// Create Code Reference In Real Time
await codeRef.set({
'code': '$_currentValue1$_currentValue2$_currentThumbValue',
'user1Id': FirebaseAuth.instance.currentUser!.uid,
'user1Name': userInfo['displayName'] ?? 'username',
'user2Id': '',
'user2Name': '',
});
/// Timer To Delete Code If No Match Found
var counter = 10;
Timer.periodic(const Duration(seconds: 1),
(timer) async {
setState(() {
isMatchVisible = false;
});
print(timer.tick);
print('Data Value During Countdown : $value');
counter--;
/// Check If User2 Exist Im Match Document
DocumentSnapshot documentSnapshot =
await FirebaseFirestore.instance
.collection('match')
.doc('$_currentValue1$_currentValue2$_currentThumbValue')
.get();
///
if (documentSnapshot.get('user2Id') != '') {
timer.cancel();
var goToPageUser1 = 2;
Timer.periodic(const Duration(seconds: 2),
(timer) async {
goToPageUser1--;
if (goToPageUser1 == 0) {
timer.cancel();
/// Go To Chat Screen
await Get.toNamed(
'/ChatScreen',
arguments:
'$_currentValue1$_currentValue2$_currentThumbValue',
parameters: {
'name1': userInfo['displayName'],
'Id1': user!.uid,
},
preventDuplicates: false,
);
}
});
} else {
if (counter == 0) {
var failCounter = 5;
Timer.periodic(const Duration(seconds: 1),
(timer) {
setState(() {
showFailResult = true;
});
failCounter--;
if (failCounter == 0) {
timer.cancel();
setState(() {
showFailResult = false;
});
}
});
/// Delete Code From Match Collection
DocumentReference docCode = FirebaseFirestore
.instance
.collection('match')
.doc(
'${_currentValue1.toString()}${_currentValue2.toString()}${_currentThumbValue.toString()}');
await docCode
.delete()
.then((value) =>
print('Match Code Deleted'))
.catchError((error) => print(
"Failed To Delete Match Code: $error"));
/// Delete Room From Real Time
await codeRef.remove();
print('Cancel timer');
timer.cancel();
setState(() {
value = false;
isMatchVisible = true;
print(
'Data Value After Countdown : $value');
});
}
}
});
/// If Code Is Exist
} else if (value == true) {
/// Update Match Document For User1 AND User2
await FirebaseFirestore.instance
.collection('match')
.doc(
'${_currentValue1.toString()}${_currentValue2.toString()}${_currentThumbValue.toString()}',
)
.update({
'user2Id':
FirebaseAuth.instance.currentUser!.uid,
'user2Name':
userInfo['displayName'] ?? 'username',
});
/// Update Match Code In Real Time
await codeRef.update({
'user2Id':
FirebaseAuth.instance.currentUser!.uid,
'user2Name':
userInfo['displayName'] ?? 'username',
});
var goToPageUser2 = 2;
Timer.periodic(const Duration(seconds: 2),
(timer) async {
goToPageUser2--;
if (goToPageUser2 == 0) {
timer.cancel();
/// Go To Chat Screen
await Get.toNamed(
'/ChatScreen',
arguments:
'$_currentValue1$_currentValue2$_currentThumbValue',
parameters: {
'name2': userInfo['displayName'],
'Id2': user!.uid,
},
preventDuplicates: false,
);
}
});
}
} on FirebaseAuthException catch (e) {
showSnackBar(context, e.message!);
}
},
child: SizedBox(
width: 100,
height: 50,
child: Center(
child: Text(
'Match'.tr,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 20),
),
),
),
),

Flutter unit testing streams that do map

I did stuck with testing streams that do some transformations before returning the value. I am using Firestore as my data storage and have a separated data layer that performs some mapping from DocumentSnapshot<T> to my model. Here is an example of the repo:
class RequestsRepository {
final CollectionReference<Request> _requestsCollection =
getIt<FirebaseFirestore>().collection('requests').withConverter<Request>(
fromFirestore: (snapshot, _) => Request.fromJson(snapshot.data()!),
toFirestore: (request, _) => request.toJson());
Stream<Request?> observe(String id) {
return _requestsCollection
.doc(id)
.snapshots()
.map((snapshot) => snapshot.exists ? snapshot.data() : null);
});
}
}
And now I'd like to cover observe(id) function with tests. Here is the solution I came to and below I'll explain why:
test('request exists', () async {
// WHEN
final stream = repository.observe(request.id);
// expected events
final events = [null, request, request..completed = true, null];
var eventIndex = 0;
// THEN
stream.listen(expectAsync1((value) async {
await Future.delayed(Duration(milliseconds: 100));
expect(value, events[eventIndex]);
eventIndex++;
}, max: -1));
// GIVEN
await firestore
.collection('requests')
.doc(request.id)
.set(request.toJson());
request.completed = true;
await firestore
.collection('requests')
.doc(request.id)
.set(request.toJson());
// reset request data
request.completed = false;
});
I tried emitsInOrder stream matcher however it fails, because I'm doing mapping inside the observe(id) function and that result in actually not request object but _MapStream<DocumentSnapshot<Request?>, Request?> instead because of the logic that stream map function follows:
Stream<S> map<S>(S convert(T event)) {
return new _MapStream<T, S>(this, convert);
}
The solution I came to did work for this case but it did not for other tests and also the solution is smelly. Any suggestions how to tests these kind of streams?
UPDATE:
Per comment from #pskink here is the demo test that fails with the same reason:
class Test {
Stream<B> observe() {
return Stream.periodic(Duration(milliseconds: 500), (i) => A(i * 10))
.map((i) => B('s${i.a}'));
}
}
class A {
final int a;
A(this.a);
bool operator ==(o) => o is A && a == o.a;
int get hashCode => a.hashCode;
}
class B {
final String b;
B(this.b);
bool operator ==(o) => o is B && b == o.b;
int get hashCode => b.hashCode;
}
void main() {
test('mapped stream: A > B', () async {
final stream = Test().observe();
expect(stream, emitsInOrder([B('s0'), B('s10'), B('s20'), B('s30')]));
});
}
It passes with the test so far.
Thanks to #pskink so far to identifying that the issue is not with stream conversion (even though logs look pretty unreadable). I had to make my Request object Equatable so it can now compare all fields appropriately. And I had to remove the await from firestore calls so that events occur really asynchronously.
Here is an example of working test:
test('request exists', () async {
// GIVEN
final timestamp = DateTime.now();
final request = Request(
'49a257e1-c0e3-4cc1-9053-aaf55197f897',
user.id,
"${user.firstName} ${user.lastName}",
user.phone ?? "",
'center',
'comment',
timestamp,
false,
false);
await firestore
.collection('requests')
.doc(request.id)
.set(request.toJson());
// WHEN
final stream = repository.observe('id', request.id);
// THEN
expect(
stream,
emitsInOrder([
Request(
'49a257e1-c0e3-4cc1-9053-aaf55197f897',
user.id,
"${user.firstName} ${user.lastName}",
user.phone ?? "",
'center',
'comment',
timestamp,
false,
false),
Request(
'49a257e1-c0e3-4cc1-9053-aaf55197f897',
user.id,
"${user.firstName} ${user.lastName}",
user.phone ?? "",
'center',
'comment',
timestamp,
false,
true),
]));
firestore
.collection('requests')
.doc(request.id)
.set({"completed": true}, SetOptions(merge: true));
});

Creating dynamic dropdown - Flutter Issue Items reads zero

Hello I tried to create a dynamic dropdown with flutter. the request to end point returns the data successfully but the DropdownButtonFormField items array is reading zero. it is not getting the data at all. Please can any one help me?
Dropdown menu items variable:
List<DropdownMenuItem> _anchorLists = List<DropdownMenuItem>();
dynamic _selectedAnchor;
void initState() {
super.initState();
_getAnchorsByProvider();
}
The Function:
_getAnchorsByProvider() async {
try {
_prefs = await SharedPreferences.getInstance();
var _anchorService = AnchorService();
var result = await _anchorService
.getAnchorsByProviderId(_prefs.getInt('providerOid'));
var anchors = json.decode(result.body);
anchors.forEach((anchor) {
setState(() {
_anchorLists.add(DropdownMenuItem(
child: Text(anchor['acronym']),
value: anchor['oid'],
));
});
});
setState(() {
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
});
return e.toString();
}
}
The Dropdown
SizedBox(height: 20),
(_anchorLists.length > 0)?
DropdownButtonFormField(
value: _selectedAnchor,
items: _anchorLists,
hint: const Text('Select your Anchor'),
onChanged: (value) {
setState(() {
_selectedAnchor = value;
});
},
)
: Text('Loading'),
Result
Values of the json:
{
"oid": 1,
"acronym": "MAAN",
"contactAddress": "Abuja",
"idCardInstruction": null,
}
Because you are retrieving a json object (Map) and not a json list (List), forEach must iterate through key and value pairs, like so:
anchors.forEach((key, value) {
_anchorLists.add(DropdownMenuItem(
child: Text(value.toString()),
value: value.toString(),
));
});
I'm not sure of the specifics of your needs so tweak accordingly, but if you only wish to provide specific values, you can specify like so:
_anchorLists.addAll([anchors['acronym'], anchors['oid']]
.map((value) => DropdownMenuItem(
child: Text(value.toString()),
value: value.toString(),
)));

The code I wrote with rxDart is not readable

I'm listening to 2 collections on firebase using RxDart. The data is being pulled, but the merge with RxDart does not go into my code. So that part passes. What could be the problem??
User and Conversation collections are listening. I can reach this data and ı can write on the console. But Rx Code passes.
#override
Stream<List<Chat?>> getAllConversation(String userId) async* {
Stream<List<Conversation?>> conversationStream = _firestore
.collection(_conversationCollectionName)
.where(
Conversation.memebersKey,
arrayContains: userId,
)
.orderBy(
Conversation.sentAtKey,
)
.snapshots()
.map(
(event) => event.docs
.map((e) => _getConversationFromDocumentSnapshot(e, e.id))
.toList(),
);
Stream<List<User?>> userStream =
_firestore.collection(_usersCollectionName).snapshots().map(
(event) => event.docs
.map((e) => _getUserFromDocumentSnapshot(
e,
))
.toList(),
);
yield* Rx.combineLatest2(
conversationStream,
userStream,
(List<Conversation?> conversations, List<User?> users) {
print('Rx içinde giriyor');
return conversations.map((conversation) {
if (conversation != null) {
var otherUser = conversation.memebers[0] == userId
? conversation.memebers[1]
: conversation.memebers[0];
bool isOwnMessage =
conversation.lastUserId == userId ? true : false;
User? user = users.firstWhere((user) {
return user!.userId == otherUser;
});
return Chat(
id: conversation.conversationId,
user: user,
message: conversation.lastMessages,
unReadMessageCount: isOwnMessage ? 0 : conversation.unRead,
isOwnMessage: isOwnMessage,
sentAt: conversation.sentAt,
senderId: userId,
);
}
}).toList();
},
);
}

How to read StateNotifierProvider.family without passing value?

I have implemented StateNotifierProvider with ".family" modifier:
class OrderReviewNotifier extends StateNotifier<OrderReviewState> {
final OrderReviewRepository repository;
OrderReviewNotifier(
this.repository,
int orderId,
) : super(OrderReviewState.initial(orderId));
Future<void> getOrderItems() async {
//.....
}
}
final orderReviewProvider = StateNotifierProvider.autoDispose
.family<OrderReviewNotifier, OrderReviewState, int>(
(ref, orderId) {
return OrderReviewNotifier(
ref.watch(orderReviewRepositoryProvider),
orderId,
);
},
);
Then in Consumer I watch it:
Consumer(
builder: (context, watch, child) {
final state = watch(orderReviewProvider(order.id));
//.....
},
);
But when I want to read it, I need to pass order.id too:
onTap: () {
context
.read(orderReviewProvider(order.id).notifier)
.getOrderItems();
},
When I want to send events to notifier from another file, I don't have order.id.
How to get out of this situation?
Thanks for any help!
I figured out.
All I needed was StateProvider.
final selectedOrderProvider = StateProvider<Order?>((ref) => null);
Then in orderReviewProvider I can easily get orderId.
final orderReviewProvider =
StateNotifierProvider.autoDispose<OrderReviewNotifier, OrderReviewState>(
(ref) {
return OrderReviewNotifier(
ref.read,
orderId: ref.watch(selectedOrderProvider).state!.id,
repository: ref.watch(orderReviewRepositoryProvider),
);
},
);
class OrderReviewNotifier extends StateNotifier<OrderReviewState> {
OrderReviewNotifier(
this.read, {
required int orderId,
required this.repository,
}) : super(OrderReviewState.initial(orderId)) {
getOrderItems();
}
final Reader read;
final OrderReviewRepository repository;
Future<void> getOrderItems() async {
state = state.copyWith(
isLoading: true,
error: null,
);
final result = await repository.getOrderItems(state.orderId);
final checkedItemIds = await repository.getCheckedItemIds(state.orderId);
if (!mounted) {
return;
}
result.when(
data: (data) {
final isAllItemsChecked = !checkedItemIds.containsValue(false) &&
checkedItemIds.length >= data.length;
state = state.copyWith(
orderItems: data,
checkedItemIds: checkedItemIds,
isAllItemsChecked: isAllItemsChecked,
);
},
error: (message) {
state = state.copyWith(
error: message,
);
},
);
state = state.copyWith(
isLoading: false,
);
}
}
The documentation describes the work with this well: link.