IOS datastore query causing performance drop/lag and slow
Description
When I run the following function:
Future<List<Questionnaire>> getUserQuestionnaireHistoryByName(
String userID, String questionnaireName) {
final questionnaireList = Amplify.DataStore.query(
Questionnaire.classType,
where: Questionnaire.USERID
.eq(userID)
.and(Questionnaire.QUESTIONNAIRENAME.eq(questionnaireName)),
sortBy: [Questionnaire.RESPONSEDATE.descending()],
);
return questionnaireList;
}
On IOS I get a performance issue, where the whole app briefly lags, while on Android I don't run into this issue at all. This performance issue happens, because I have a button and when the button is clicked, we immediately transition to a new page, where when that page is being loaded up, there is defn some lag and frame drop, when running a amplify datastore query during post frame, which also takes a while.
Categories
- [ ] Analytics
- [ ] API (REST)
- [ ] API (GraphQL)
- [ ] Auth
- [ ] Authenticator
- [x] DataStore
- [ ] Notifications (Push)
- [ ] Storage
Steps to Reproduce
Main.dart build function
@override
Widget build(BuildContext context) {
super.build(context);
pageList = [const PrivacyPage(), const TermsPage()];
return
PopScope(
canPop: false,
onPopInvoked: (didPop) {
setState(() {
_selectedIndex = 0;
});
},
child: Scaffold(
backgroundColor: FlavorColor.backgroundSpineScaffoldColor,
body: pageList[_selectedIndex],
bottomNavigationBar:
BottomNavigationBar(
unselectedItemColor: Colors.grey.shade400,
selectedItemColor:
FlavorColor.HOME_SCREEN_SELECTED_BOTTOMBAR_TEXT_COLOR,
onTap: _onItemClicked,
items: [BottomNavigationBarItem(icon: Icon(Icons.home), label: "home"),
BottomNavigationBarItem(icon: Icon(Icons.local_police_outlined), label: "loading"),])
),
);
}
Privacy page:
import 'package:ap_sci_app/helper/locator.dart';
import 'package:ap_sci_app/helper/start_up_service.dart';
import 'package:flutter/material.dart';
class PrivacyPage extends StatelessWidget {
const PrivacyPage({super.key});
@override
Widget build(BuildContext context) {
final StartUpSharedPreferenceService _suSharedPrefService =
getIt<StartUpSharedPreferenceService>();
final bool useMobileLayout = MediaQuery.of(context).size.shortestSide < 600;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(child:Text("Home", style: TextStyle(color: Colors.white)))
]);
}
}
Terms Page
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:ap_sci_app/backend_features/auth/aws_auth_functions.dart';
import 'package:ap_sci_app/helper/locator.dart';
import 'package:ap_sci_app/helper/start_up_service.dart';
import 'package:ap_sci_app/models/Questionnaire.dart';
import 'package:ap_sci_app/screens/settings/profile/sci_helper.dart';
import 'package:flutter/material.dart';
class TermsPage extends ConsumerStatefulWidget {
const TermsPage({super.key});
@override
ConsumerState<TermsPage> createState() => _TermsPageState();
}
class _TermsPageState extends ConsumerState<TermsPage> {
final StartUpSharedPreferenceService _suSharedPrefService =
getIt<StartUpSharedPreferenceService>();
Future<List<Questionnaire>> getUserQuestionnaireHistoryByName(
String userID, String questionnaireName) async{
final questionnaireList = await Amplify.DataStore.query(
Questionnaire.classType,
where: Questionnaire.USERID
.eq(userID)
.and(Questionnaire.QUESTIONNAIRENAME.eq(questionnaireName)),
sortBy: [Questionnaire.RESPONSEDATE.descending()],
);
return questionnaireList;
}
Future<void> questionnaireFunction() async {
String userID = await getUserID();
await getUserQuestionnaireHistoryByName(
userID, _suSharedPrefService.dailyCheckInName);
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await questionnaireFunction();
setState(() {
done = true;
});
});
}
bool done = false;
@override
Widget build(BuildContext context) {
final bool useMobileLayout = MediaQuery.of(context).size.shortestSide < 600;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(child:Text(done ? "Done" :"Loading", style: TextStyle(color: Colors.white)))
]);
}
}
Using the files above, i have a simple program where I am just switching between two pages using bottom navigator bar, running the amplify datastore query, during a post frame callback for the loading page, and you can see a performance lag for some reason only on IOS, and it takes a really long time as well.
No issues on Android. Any possible reason/explanation why this is happening?
Screenshots
Videos: IOS: https://github.com/user-attachments/assets/15fb62ce-05e4-40a4-a9a4-a12ff9326b90
Android: https://github.com/user-attachments/assets/f7fa1b78-7b76-422d-8868-c3e864948477
Platforms
- [x] iOS
- [ ] Android
- [ ] Web
- [ ] macOS
- [ ] Windows
- [ ] Linux
Flutter Version
3.27.4
Amplify Flutter Version
2.6.1
Deployment Method
Amplify CLI (Gen 1)
Schema
# This "input" configures a global authorization rule to enable public access to
# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/authorization-rules
# input AMPLIFY {
# globalAuthRule: AuthRule = { allow: public }
# } # FOR TESTING ONLY!
type Questionnaire
@model
@auth(
rules: [
{ allow: owner, ownerField: "userID" }
{ allow: groups, groups: ["Admin", "Researcher"] }
]
) {
id: ID!
user: User @belongsTo
responsedate: AWSDate!
question: [Question] @hasMany
questionnaireName: String!
userID: String
createdAt: AWSDateTime
updatedAt: AWSDateTime
status: Boolean
project: Project @belongsTo
researchers: [Researcher]
@manyToMany(relationName: "ResearcherQuestionnaire")
completionStatus: String
questionnaireLocation: String
questionnaireType: String
distributionFrequency: String
pushReminders: [String]
schedule: Schedule @belongsTo
activities: [Activity] @hasMany
inPersonVisit: InPersonVisit @belongsTo
details: AWSJSON
}
Hello @Kam5678, thanks for taking the time to reach out. I'm trying to replicate your backend, but the schema is missing some type definitions. Could you please provide the rest of your schema.
Hi @tyllark , thank you for looking into this!!
Would it be possible for me to send the schema over privately?
Hello @Kam5678, I'm looking into a method for sending the schema in a secure manner. In the meantime I've been trying to reproduce the issue with a simplified versions of the missing models without any luck:
type Project @model {
id: ID!
questionnaires: [Questionnaire] @hasMany
name: String!
}
How many entries are returned from your getUserQuestionnaireHistoryByName call on both iOS and Android? When I stored ~10,000 Questionnaires locally it was taking ~8 seconds to run Amplify.DataStore.query(Questionnaire.classType);. Even with 100-200 entries it was taking ~.2 seconds to complete which makes awaiting a DataStore query not ideal within the Flutter render cycle. While we investigate the performance of DataStore you can use a StateManagement solution such as Listenables or avoid awaiting Amplify.DataStore.query within the Flutter render cycle to make this performance issue feel more responsive.
@override
void initState() {
super.initState();
questionnaireFunction().then(
(_) => setState(() {
done = true;
}),
);
}
Sounds good, thank you for looking into that @tyllark
So in dynamoDB, we have about 600 questionnaire entries stored in total. As for fetching, when fetching on IOS, about 120 entries are being returned, both on IOS and Android.
Android execution time is 0.01s basically instant. But IOS takes about 9 seconds, which is really weird.
Even when not awaiting, and just running the query without an await(as your code above), still run into the same problem on IOS.
From what I observed, whenever the query starts running, performance issue starts, and when query ends, performance issue ends.
I'm not sure if this is because our model is complex, but the fact that it runs completely fine on Android, is really weird.
Hello @Kam5678, sorry for the delayed response. Flutter doesn't implement DataStore itself, instead we wrap the Amplify Swift/Android DataStore implementations, which can lead to some discrepancies between the platforms.
-
I've been unable to reproduce the issue with ~600 entries on my end. I still want to try with your schema to see if more complex models are causing the slowdown. While schemas are typically safe to post here, you can direct message my Amazon Discord
tyllark_58391if you don't feel comfortable posting it on Github. -
Could you please let me know if you are testing on a physical iOS phone or a simulator and if you are able to reproduce the issue on multiple devices.
-
Finally could you rerun the test on Android/iOS with the below logging code added after you configure Amplify. This will help us determine if the issue is related to syncing with DynamoDB or querying the local DataBase.
Amplify.Hub.listen(HubChannel.DataStore, (hubEvent) {
String hubPayload = 'No HubPayload';
switch (hubEvent.type) {
case DataStoreHubEventType.ready:
break; //No Payload
case DataStoreHubEventType.networkStatus:
final payload = hubEvent.payload as NetworkStatusEvent?;
hubPayload = 'active: ${payload?.active}';
case DataStoreHubEventType.subscriptionsEstablished:
break; //No Payload
case DataStoreHubEventType.syncQueriesStarted:
final payload = hubEvent.payload as SyncQueriesStartedEvent?;
hubPayload = 'models: ${payload?.models}';
case DataStoreHubEventType.modelSynced:
final payload = hubEvent.payload as ModelSyncedEvent?;
hubPayload =
'modelName: ${payload?.modelName}, isFullSync: ${payload?.isFullSync}, isDeltaSync: ${payload?.isDeltaSync}, added: ${payload?.added}, updated: ${payload?.updated}, deleted: ${payload?.deleted}';
case DataStoreHubEventType.syncQueriesReady:
break; //No Payload
case DataStoreHubEventType.outboxMutationEnqueued:
final payload = hubEvent.payload as OutboxMutationEvent?;
hubPayload =
'modelName: ${payload?.modelName}, elementType: ${payload?.element.model.runtimeType}';
case DataStoreHubEventType.outboxMutationProcessed:
final payload = hubEvent.payload as OutboxMutationEvent?;
hubPayload =
'modelName: ${payload?.modelName}, elementType: ${payload?.element.model.runtimeType}';
case DataStoreHubEventType.outboxMutationFailed:
break; //No Payload
case DataStoreHubEventType.outboxStatus:
final payload = hubEvent.payload as OutboxStatusEvent?;
hubPayload = 'isEmpty: ${payload?.isEmpty}';
case DataStoreHubEventType.subscriptionDataProcessed:
final payload = hubEvent.payload as SubscriptionDataProcessedEvent?;
hubPayload =
'modelName: ${payload?.modelName}, elementType: ${payload?.element.model.runtimeType}, version: ${payload?.element.version}, lastChangedAt: ${payload?.element.deleted}, elementType: ${payload?.element.deleted}';
}
safePrint(
'${DateTime.now().toUtc()} - DataStoreEvent - ${hubEvent.eventName} - $hubPayload',
);
});
Hi @tyllark,
No worries and thank you for replying!
I sent a message over through the discord, with everything requested
- As for the testing, I tested both on IOS emulator and physical device in debug mode and was able to reporduce the issue on both!
Hi @Kam5678, thanks for providing the requested information, I'll let @tyllark know and we will investigate further
Hi @ekjotmultani ,
For sure sounds good thank you!!
Hello @Kam5678, thank you for providing the schema, I've been able to reproduce the issue on my side. The performance hit seems to be related to saving/querying types with nested types. We are still investigating and will back to you here once we have an update.
Hi @tyllark,
Really appreciate the update and thank you for keeping me in the loop!!
Sounds good, really appreciate all the time the team is putting into resolving this!!
Hello @Kam5678, The Amplify Swift team was able to reproduce the issue on their side, so I've created an issue in their repository. Once they have a fix we will update Amplify Flutter to use the latest Amplify Swift version.
Sounds good thanks for the update @tyllark!