Hello you have mentioned the link previously and we have done what was mentioned in the docs, but we are not retrieving any members when calling getMembers(). How can we retrieve members information in a community? Can we get example code of retrieving all members in a community? Because we are not retrieving any members like mentioned above.
May I just double-check one thingāare you implementing a chat (channel) or a community feature?
We are implementing community feature.
Below is an example of our code, where we are trying to retrieve all members in a specific community:
import 'package:amity_sdk/amity_sdk.dart';
import 'package:animations/animations.dart';
import 'package:app/constants/app_theme.dart';
import 'package:app/logger.dart';
import 'package:app/screens/home/camera_screen.dart';
import 'package:app/screens/home/social/invite_members_screen.dart';
import 'package:app/screens/home/social/trip_group_edit_screen.dart';
import 'package:app/screens/home/view_photo/view_amity_photo_screen.dart';
import 'package:app/services/amity/social/community_service.dart';
import 'package:app/services/amity/user_service.dart';
import 'package:app/services/image_cache_manager.dart';
import 'package:app/services/image_service.dart';
import 'package:app/services/lock_time_service.dart';
import 'package:app/widgets/custom_bottom_navigation_widget.dart';
import 'package:app/widgets/shimmer_album_photo_widget.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:quickalert/models/quickalert_type.dart';
import 'package:quickalert/widgets/quickalert_dialog.dart';
import 'package:responsive_grid/responsive_grid.dart';
import 'package:share_plus/share_plus.dart';
// ignore: must_be_immutable
class TripGroupScreen extends StatefulWidget {
AmityCommunity community;
TripGroupScreen({super.key, required this.community});
@override
State<TripGroupScreen> createState() => _TripGroupScreenState();
}
class _TripGroupScreenState extends State<TripGroupScreen> {
final communityService = Get.find<CommunityService>();
final locktimeService = Get.find<LockTimeService>();
RxList<AmityPost> lockedPosts = <AmityPost>[].obs;
RxList<AmityPost> unlockedPosts = <AmityPost>[].obs;
final userService = Get.find<UserService>();
final imageService = Get.find<ImageService>();
bool isMyTrip = false;
final imageCacheManager = ImageCacheManager();
late PagingController<AmityPost> _postController;
final _amityChannelMembers = <AmityChannelMember>[];
late PagingController<AmityChannelMember> _channelMembersController;
void _retrieveTripCommunity() async {
var updatedCommunity =
await communityService.getCommunity(widget.community.communityId!);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {
widget.community = updatedCommunity;
});
});
}
@override
void initState() {
isMyTrip = widget.community.communityId == userService.tripCommunityId;
_queryChannelMembers();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_channelMembersController.fetchNextPage();
_postController.fetchNextPage();
});
initializePageController();
super.initState();
}
@override
void dispose() {
_postController.dispose();
imageCacheManager.clearCache();
super.dispose();
}
Future<void> initializePageController({bool isForce = false}) async {
if (isForce) {
_postController.dispose();
}
_postController = PagingController(
pageFuture: (token) => AmitySocialClient.newFeedRepository()
.getCommunityFeed(widget.community.communityId!)
.getPagingData(token: token, limit: 20),
pageSize: 20,
)..addListener(
() {
if (_postController.error == null) {
//handle results, we suggest to clear the previous items
//and add with the latest _postController.loadedItems
lockedPosts.clear();
unlockedPosts.clear();
try {
//? categorize the posts by comparing previous unlocked time
for (AmityPost amityPost in _postController.loadedItems) {
DateTime postCreatedAt = amityPost.createdAt!;
//? if previousUnlockedTime is null then lock all photos
if (locktimeService.previousUnlockedTime == null) {
lockedPosts.add(amityPost);
continue;
}
//? if postCreatedAt is before or equal to the previousUnlockedTime then unlock, else lock
else if (postCreatedAt.isBefore(
locktimeService.previousUnlockedTime!.toUtc()) ||
postCreatedAt.isAtSameMomentAs(
locktimeService.previousUnlockedTime!.toUtc())) {
//* add to unlockedPosts
unlockedPosts.add(amityPost);
} else {
//* add to lockedPosts
lockedPosts.add(amityPost);
}
}
AppLogger.logInfo(
'Updated lockedPosts: ${lockedPosts.length} unlockedPosts: ${unlockedPosts.length}');
} catch (exception) {
AppLogger.logError('Error in categorizing posts: $exception');
}
lockedPosts.refresh();
unlockedPosts.refresh();
//update widgets
} else {
//error on pagination controller
//update widgets
}
},
);
}
Future<bool> loadMoreFeed({bool isForce = false}) async {
if (_postController.hasMoreItems || isForce) {
lockedPosts.clear();
unlockedPosts.clear();
await _postController.fetchNextPage();
return true;
} else {
return false;
}
}
void _queryChannelMembers() {
_channelMembersController = PagingController(
pageFuture: (token) => AmityChatClient.newChannelRepository()
.membership(widget.community.channelId!)
.getMembers()
.getPagingData(token: token, limit: 20),
pageSize: 20,
)..addListener(
() {
if (_channelMembersController.error == null) {
//handle results, we suggest to clear the previous items
//and add with the latest _postController.loadedItems
_amityChannelMembers.clear();
_amityChannelMembers.addAll(_channelMembersController.loadedItems);
//update widgets d
} else {
//error on pagination controller
//update widgets
}
},
);
}
@override
Widget build(BuildContext context) {
return PopScope(
onPopInvoked: (bool) async {
Get.back(result: true);
},
child: Scaffold(
appBar: AppBar(
scrolledUnderElevation: 0,
leading: BackButton(
onPressed: () {
Get.back(result: true);
},
),
title: Text(widget.community.displayName ?? "N/A"),
backgroundColor: AppColors.primaryBackground,
actions: [
PopupMenuButton<String>(
color: AppColors.secondaryBackground,
icon: const Icon(Icons.more_vert),
onSelected: (String result) {
// Handle the action based on the selected value
switch (result) {
case 'edit':
_editCommunity();
// Handle edit action
break;
case 'leave':
_leaveCommunity();
break;
}
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
const PopupMenuItem<String>(
value: 'edit',
child: Text('Edit Trip'),
),
const PopupMenuItem<String>(
value: 'leave',
child: Text('Leave Trip'),
),
// Add more items as needed
],
),
],
),
backgroundColor: AppColors.secondaryBackground,
body: StreamBuilder<AmityCommunity>(
stream: widget.community.listen.stream,
initialData: widget.community,
builder: (context, snapshot) {
var memberCount = snapshot.data!.membersCount!;
return memberCount > 1
? unlockedPosts.isEmpty
? Column(children: [
_buildHeader(),
_buildMemberSection(),
Expanded(child: _buildNoImagesView()),
])
: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: Column(
children: [
_buildHeader(),
_buildMemberSection(),
_buildMonthlyRecap(),
// Add other sections here as needed
],
),
)
: Column(
children: [
_buildHeader(),
_buildNoMembersInviteWidget(),
],
);
},
),
bottomNavigationBar: const CustomBottomNavigationWidget(
selectedItem: NavigationItem.social,
),
),
);
}
Widget _buildHeader() {
return Padding(
padding: const EdgeInsets.only(bottom: 6),
child: Container(
width: double.infinity,
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
color: AppColors.primaryBackground,
),
padding: const EdgeInsets.all(20.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
widget.community.avatarImage == null
? CircleAvatar(
radius: 60,
child: Text(
widget.community.displayName?[0] ?? "N/A",
style: const TextStyle(
color: Colors.white,
fontSize: 60,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
)
: CircleAvatar(
radius: 60,
backgroundImage: CachedNetworkImageProvider(
widget.community.avatarImage!
.getUrl(AmityImageSize.MEDIUM),
),
),
const SizedBox(
width: 20,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
//* Display Name
Text(
widget.community.displayName ?? "N/A",
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
Row(
children: [
GestureDetector(
onTap: () {
if (mounted) {
QuickAlert.show(
context: context,
type: QuickAlertType.info,
title: 'My Trip is...',
text:
'Only ONE trip can be "My Trip"\nIn camera screen you can select \n"My Trip" to send photos to this group.',
);
}
},
child: Row(
children: [
isMyTrip
? const Icon(
Icons.trip_origin,
color: AppColors.interactiveElements,
size: 18,
)
: const Icon(
Icons.help_outline,
color: AppColors.secondaryText,
size: 18,
),
const SizedBox(width: 2),
Text(
" Set as My Trip",
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14,
color: isMyTrip
? AppColors.interactiveElements
: AppColors.secondaryText,
),
),
],
),
),
const SizedBox(width: 8),
SizedBox(
height: 30,
child: FittedBox(
fit: BoxFit.fill,
child: Switch(
inactiveThumbColor: AppColors.secondaryText,
trackOutlineColor: MaterialStateProperty.resolveWith(
(final Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return null;
}
return AppColors.secondaryText;
},
),
value: isMyTrip,
onChanged: (bool value) {
if (value) {
//? Set as my trip
userService.setTripCommunityId(
widget.community.communityId!,
widget.community.displayName ?? "",
);
} else {
//? Remove as my trip
if (userService.tripCommunityId ==
widget.community.communityId) {
userService.setTripCommunityId("", "");
}
}
setState(() {
isMyTrip = value;
});
},
activeColor:
Colors.orange, // Change active color to orange
),
),
),
],
),
const SizedBox(height: 12),
//* Tag
Row(
children: [
Container(
height: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: AppColors.secondaryBackground,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Center(
child: Text(
(widget.community.tags?[0])?.capitalize ?? "",
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
),
),
),
const SizedBox(width: 8),
//* Created Date
Container(
height: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: AppColors.secondaryBackground,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Center(
child: Text(
formatDate(widget.community.createdAt!),
style: const TextStyle(
color: AppColors.primaryText,
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
),
),
),
],
),
],
)
// Add more widgets for date, BFFs, location, etc.
],
),
),
);
}
Widget _buildMemberSection() {
return Padding(
padding: const EdgeInsets.only(
bottom: 0,
),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
decoration: BoxDecoration(
color: AppColors.primaryBackground,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 2),
child: Align(
alignment: Alignment.bottomCenter,
child: Text(
'Members ${widget.community.membersCount ?? 0}',
style: const TextStyle(color: Colors.white, fontSize: 18),
),
),
),
ElevatedButton(
onPressed: () {
Get.to(
() => InviteMemberScreen(
inviteType: InviteType.trip,
communityId: widget.community.communityId!,
),
)?.then((value) {
setState(() {
_retrieveTripCommunity();
});
});
},
style: ElevatedButton.styleFrom(
primary: AppColors.interactiveElements),
child: const Text(
'+ Invite',
style: TextStyle(
color: AppColors.primaryText,
),
),
),
],
),
//? Members Circle Avatar Profile
SizedBox(
height: 60, // Adjust the height as needed
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _amityChannelMembers.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: CircleAvatar(
radius: 40,
backgroundImage: CachedNetworkImageProvider(
widget.community.avatarImage!
.getUrl(AmityImageSize.MEDIUM),
),
),
);
},
),
),
const SizedBox(height: 8),
],
),
),
);
}
Widget _buildMonthlyRecap() {
// Placeholder for monthly recap section
return Padding(
padding: const EdgeInsets.only(top: 6),
child: Container(
width: double.infinity,
decoration: const BoxDecoration(
color: AppColors.primaryBackground,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.only(top: 20, left: 20),
child: Text(
"Trip Recap",
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
const SizedBox(height: 8),
const Padding(
padding: EdgeInsets.only(left: 20),
child: Text(
"Here's what happened...",
style: TextStyle(color: Colors.grey),
),
),
const SizedBox(height: 8),
_galleryWidget(),
],
),
),
);
}
Widget _buildNoMembersInviteWidget() {
// Placeholder for monthly recap section
return Expanded(
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(16.0),
decoration: const BoxDecoration(
color: AppColors.primaryBackground,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"You're the only one here!",
style: TextStyle(color: Colors.white, fontSize: 18),
),
const SizedBox(height: 20),
//* Invite Friends Button
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: GestureDetector(
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
final screenHeight = MediaQuery.of(context).size.height;
final double desiredHeight = screenHeight * 4 / 5;
return Container(
height: desiredHeight,
decoration: const BoxDecoration(
color: AppColors.primaryBackground,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
padding: const EdgeInsets.only(top: 20),
child: InviteMemberScreen(
inviteType: InviteType.trip,
communityId: widget.community.communityId!,
),
);
},
).then((value) {
setState(() {
_retrieveTripCommunity();
});
});
},
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(20),
),
child: const Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.add_reaction, color: Colors.white),
SizedBox(width: 8),
Text(
"Invite Friends",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
),
),
const SizedBox(height: 16),
const Text(
"or",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.secondaryText,
),
),
const SizedBox(height: 16),
//* Share link button
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: GestureDetector(
onTap: () {
Share.share('Hey friends! Join my group https://imhome.app');
},
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(20),
),
child: const Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.ios_share,
color: Colors.white,
size: 20,
),
SizedBox(width: 6),
Text(
"Share a link",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
),
),
const SizedBox(height: 40),
],
),
),
);
}
Future<void> _leaveCommunity() async {
try {
await CommunityService().leaveCommunity(widget.community.communityId!);
Fluttertoast.showToast(
msg: "Leave Community Success",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.TOP,
timeInSecForIosWeb: 1,
backgroundColor: AppColors.interactiveElements,
textColor: Colors.white,
fontSize: 16.0,
);
Get.back(result: true);
} catch (exception) {
if (mounted) {
QuickAlert.show(
context: context,
type: QuickAlertType.error,
title: 'Oops...',
text: 'Error leaving community. ${exception.toString()}',
);
}
AppLogger.logError("Error leaving community: $exception");
}
}
Widget _galleryWidget() {
return Obx(() {
// Check if the list is empty
if (unlockedPosts.isEmpty) {
return Container(); // Return an empty container if there are no posts
}
// Group the posts by 'Month Year'
Map<String, List<AmityPost>> groupedPosts = {};
for (var post in unlockedPosts) {
DateTime fileDate = post.createdAt!.toLocal();
String monthYear = DateFormat('MMMM yyyy').format(fileDate);
if (groupedPosts[monthYear] == null) {
groupedPosts[monthYear] = [];
}
groupedPosts[monthYear]!.add(post);
}
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: groupedPosts.length,
itemBuilder: (context, index) {
String monthYear = groupedPosts.keys.elementAt(index);
List<AmityPost> postsForMonthYear = groupedPosts[monthYear]!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(
left: 16,
),
child: Text(
monthYear,
style: const TextStyle(
fontSize: 28, fontWeight: FontWeight.bold),
),
),
ResponsiveStaggeredGridList(
desiredItemWidth: (MediaQuery.of(context).size.width - 80) /
2, // Adjust based on your needs
minSpacing: 10, // You can adjust the spacing
physics: const NeverScrollableScrollPhysics(),
children: postsForMonthYear.map((post) {
//? Animated Container
return OpenContainer(
onClosed: (data) {
//? if true then retrieve the posts
if (data == true) {
setState(() {
initializePageController(isForce: true);
});
}
},
openColor: AppColors.primaryBackground,
closedColor: AppColors.primaryBackground,
transitionType: ContainerTransitionType.fade,
transitionDuration: const Duration(milliseconds: 500),
closedShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
closedBuilder:
(BuildContext _, VoidCallback openContainer) {
return GestureDetector(
onTap: openContainer,
child: ClipRect(
child: CachedNetworkImage(
imageUrl: (post.children!.first.data! as ImageData)
.image!
.getUrl(AmityImageSize.FULL),
fit: BoxFit.cover,
placeholder: (context, url) =>
const ShimmerAlbumPhotoWidget(
widthDivide: 2,
),
errorWidget: (context, url, error) => const Center(
child: Text('Error loading image')),
),
),
);
},
openBuilder: (BuildContext context, VoidCallback _) {
return ViewAmityPhotoScreen(
posts: postsForMonthYear,
index: postsForMonthYear.indexOf(post),
);
},
);
}).toList(),
),
],
);
},
);
});
}
void _editCommunity() {
Get.to(
() => TripGroupEditScreen(
community: widget.community,
),
)?.then((value) {
_retrieveTripCommunity();
});
}
Widget _buildNoImagesView() {
return Padding(
padding: const EdgeInsets.only(top: 6),
child: Container(
width: double.infinity,
decoration: const BoxDecoration(
color: AppColors.primaryBackground,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"š» No Images Yet...",
style: TextStyle(color: Colors.white, fontSize: 20),
),
SizedBox(height: 4),
Text(
"Begin your trip by taking photos with your friends!",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 14),
),
],
),
),
);
}
}
String formatDate(DateTime dateTime) {
return "${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')}";
}
Based on your code, it seems thereās a mix-up between chat(channel) and community functionalities. For retrieving community members, please refer to this documentation: Query Community Members | Amity Docs, instead of using queryChannelMembers().
There is a issue with Query Community Members | Amity Docs when selecting flutter section we are unable to see the whole code script.
Can you paste the code for querying the community members?
FYI there is issue with all of the documentations for code snippets for flutter we are unable to scroll through the code snippets.
Thank you for bringing this to our attention. Weāll have our team investigate the document issue. Regarding the code, we will provide them shortly
Hereās the code:
final _amityCommunityMembers = <AmityCommunityMember>[];
late PagingController<AmityCommunityMember> _communityMembersController;
// Available sort options
// AmityCommunityMembershipSortOption.LAST_CREATED;
// AmityCommunityMembershipSortOption.FIRST_CREATED;
// Available filter options
// AmityCommunityMembershipFilter.ALL;
// AmityCommunityMembershipFilter.MEMBER;
// AmityCommunityMembershipFilter.NOT_MEMBER;
void searchCommunityMembers(
String communityId,
String keyword,
AmityCommunityMembershipSortOption sortOption,
AmityCommunityMembershipFilter filter) {
_communityMembersController = PagingController(
pageFuture: (token) => AmitySocialClient.newCommunityRepository()
.membership(communityId)
.searchMembers(keyword)
.filter(filter)
.sortBy(sortOption)
.roles([
'community-moderator'
]) //optional to query specific members by roles
.getPagingData(token: token, limit: 20),
pageSize: 20,
)..addListener(
() {
if (_communityMembersController.error == null) {
//handle results, we suggest to clear the previous items
//and add with the latest _controller.loadedItems
_amityCommunityMembers.clear();
_amityCommunityMembers
.addAll(_communityMembersController.loadedItems);
//update widgets
} else {
//error on pagination controller
//update widgets
}
},
);
}