Parsing deeply nested GraphQL Responses - flutter

I have a GraphQL schema which I've created mutations & queries from for my Flutter App :
type Mutation {
accounts: AccountsMutation!
}
type Query {
accounts: AccountsQuery!
}
type AccountsMutation {
auth: AuthenticationController!
}
type AccountsQuery {
auth: AuthenticationQueryController!
}
type AuthenticationController {
forgotPassword(email: Email!): Boolean!
resetPassword(
email: Email!
password: Password!
confirmPassword: Password!
code: String!
): IdentityResult!
login(email: Email!, password: Password!, rememberMe: Boolean! = false): User
tokenLogin(
email: Email!
password: Password!
rememberMe: Boolean! = false
): TokenLogin
refreshAccessToken(refreshToken: RefreshToken!): RefreshedAccessToken!
logOut: Boolean!
register(
email: Email!
password: Password!
confirmPassword: Password!
locale: Locale
timeZone: UserTimeZone
): User
}
type AuthenticationQueryController {
currentUser: User!
frontServiceToken: String!
}
scalar BOAccessToken
scalar BORefreshToken
scalar AccessToken
scalar Email
scalar ExternalId
type IdentityError {
code: String
description: String
}
type IdentityResult {
succeeded: Boolean!
errors: [IdentityError]
}
scalar Locale
scalar Password
type RefreshedAccessToken {
isValid: Boolean!
accessToken: AccessToken
expiresIn: Int!
}
scalar RefreshToken
scalar RoleId
scalar RoleName
scalar SecurityStamp
type TokenLogin {
user: User!
accessToken: AccessToken!
refreshToken: RefreshToken!
expiresIn: Int!
}
type User {
id: UserId!
firstName: String
lastName: String
locale: Locale!
timeZone: UserTimeZone!
email: Email!
firebaseToken: String
}
scalar UserId
scalar UserTimeZone
scalar UserTokenId
As you can see, my queries & mutations are deeply nested, how would you recommend parsing my responses ?
Currently I've created an Accounts() class that than parses an Auth() than than parses...
But I feel I'm creating unnecessary intermediary classes with this approach, are there better approaches more suited to GraphQL ?

Go to this Link:
https://pub.dev/packages/graphql_flutter
Use this :
graphql_flutter: ^5.1.0
Example:
import 'package:flutter/material.dart';
import 'package:trash_themes/themes.dart';
import './graphql_bloc/main.dart' show GraphQLBlocPatternScreen;
import './graphql_widget/main.dart' show GraphQLWidgetScreen;
import 'fetchmore/main.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GraphQL Flutter Demo',
theme: DraculaTheme().makeDarkTheme(context: context),
home: Builder(
builder: (BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('GraphQL Demo App'),
),
body: Center(
child: Column(
children: <Widget>[
Spacer(),
Flexible(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<GraphQLWidgetScreen>(
builder: (BuildContext context) =>
GraphQLBlocPatternScreen(),
),
);
},
child: const Text('GraphQL BloC pattern'),
)),
Spacer(),
Flexible(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<GraphQLWidgetScreen>(
builder: (BuildContext context) =>
const GraphQLWidgetScreen(),
),
);
},
child: const Text('GraphQL Widget'),
)),
Spacer(),
Flexible(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<FetchMoreWidgetScreen>(
builder: (BuildContext context) =>
const FetchMoreWidgetScreen(),
),
);
},
child: const Text('Fetchmore (Pagination) Example'),
)),
],
),
),
),
),
);
}
}

Related

HOW DO I SEND DATA FROM A SCREEN TO ANOTHER SCREEN

itemBuilder: (context, index) => MyImage( image: API.image +'/' `your text`+snapshot.data[index['MainPicture'].toString(), title: snapshot.data[index]['productName'],`your text`
subname: snapshot.data[index]['productSubname'],`your text`
price: snapshot.data[index][price].toString(),`your text`
discount: '% ' +
snapshot.data[index]['productDiscount'].toString(),`your text`
),
I want these parametres to make them to another Screen your text
Use a navigator like go_router
Later follow the steps accordingly to pass your parameters.
Definition
GoRoute(
path: '/sample/:id1/:id2', 👈 Defination of params in the path is important
name: 'sample',
builder: (context, state) => SampleWidget(
id1: state.params['id1'],
id2: state.params['id2'],
),
),
Passing the params
ElevatedButton(
onPressed: () {
var param1 = "param1";
var param2 = "param2";
context.goNamed("sample", params: {'id1': param1, 'id2': param2});
},
child: const Text("Hello"),
),
Receiver widget:
class SampleWidget extends StatelessWidget {
String? id1;
String? id2;
SampleWidget({super.key, this.id1, this.id2});
#override
Widget build(BuildContext context) {
...
}
}
And refer this answer: Flutter: go_router how to pass multiple parameters to other screen?

NotifyListeners is not rebuilding Consumer Widget

I am trying to build a chat application. but whenever i call a method to update the chat list
everything works, but the widget is not rebuilding.
i have tried with context.read() and context.watch() methods : Not working
i have tried with Provider.of(context,listen:true); : Not working
i have tried with ChangeNotifierProxyProvider : Not working
only restarting rebuilds the ui. any help would be appreciated.
below is my code.
void main() {
runApp(MultiProvider(providers: [
ChangeNotifierProvider(create: (context) => UsersProvider()),
ChangeNotifierProvider(create: (context) => ChatProvider()),
], child: const MyApp()));
}
class UsersProvider extends ChangeNotifier {
UsersProvider(){
getChats();
}
List<Chats> get chats=> _chats;
List<Chats> _chats = [];
Future getChats() async {
final json = await UsersRepo().getChatLists();
if (json == null) {
return;
}
if (json['status']) {
ChatListModel _model = ChatListModel.fromJson(json);
_chats = _model.chats ?? [];
notifyListeners();
}
}
}
and the ui looks like
class ChatList extends StatelessWidget {
const ChatList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.appBg,
appBar: AppBar(
title: Text('Users'),
backgroundColor: AppColors.appBgLight,
),
body: chatListWidget(context),
);
}
Widget chatListWidget(BuildContext context) {
return Consumer<UsersProvider>(
builder: (BuildContext context, provider, Widget? child) {
return ListView.separated(
padding: EdgeInsets.symmetric(vertical: 20),
itemCount: provider.chats.length,
separatorBuilder: (c, i) => sbh(15),
itemBuilder: (c, i) {
Chats chat = provider.chats[i];
return ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (c) {
return ChatScreen(
user: Users(
name: chat.receiver?.name, sId: chat.receiver?.id),
);
},
),
);
context.read<ChatProvider>().connectToUser(chat.receiver?.id);
},
leading:
userImageWidget((chat.receiver?.name ?? "")[0].toUpperCase()),
subtitle: Text(
chat.lastMessage ?? "",
style: TextStyle(
color: Colors.white70,
fontSize: 12,
),
),
trailing: CircleAvatar(
radius: 15,
backgroundColor: Colors.green,
child: Center(
child: Text(
'${chat.unReadMessageCount}',
style: TextStyle(fontSize: 12, color: Colors.white),
)),
),
title: nameWidget(chat.receiver?.name ?? ''),
);
},
);
},
);
}
}
Chats model class
class Chats {
String? lastMessage;
String? messageTime;
int? unReadMessageCount;
Chats(
{this.lastMessage,
this.messageTime,
this.unReadMessageCount});
Chats.fromJson(Map<String, dynamic> json) {
lastMessage = json['lastMessage'];
messageTime = json['messageTime'];
unReadMessageCount = json['unReadMessageCount'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['lastMessage'] = this.lastMessage;
data['messageTime'] = this.messageTime;
data['unReadMessageCount'] = this.unReadMessageCount;
return data;
}
}
I suspect that your getChats() method is failing before it can execute the notifyListeners method.
You see, any "if" statement in dart has to evaluate to a boolean, so unless json['status'] returns true/false then the expression:
if (json['status'])
will throw an error that will end up in a failed future to be returned from your getChats() method.
It would be better to use a more specific condition for your if statement, maybe something like this (if json['status'] is supposed to contain the http response code for the call):
if (json['status'] == 200)
In the case json['status'] does indeed contains a boolean then there could be a few other reasons for your widget not rebuilding:
1- json['status'] always evaluates to false
2- The call to get the chats fails but you get a silent error since you don't have a handler for the failed future that is returned by the getChats method

Assume a list is being assigned the query of a SQLite database, how to ensure that the list is not null? Flutter

I am making a Flutter application with SQLite database in it with the sqflite package. I have a database helper class with the necessary methods. In one of the pages I want to display the data as a list of cards. I also have an image stored to the database, as such in the same page I have to convert the image back to a File. In the class of that page, named DataPage, I made a method called query which calls the query method of the database and assigns that value to a list called listLokasi. I also made a method called convert which calls List.generate with one of the arguments being listLokasi.length. Meanwhile I placed these 2 methods inside the constructor for _DataPageState as DataPage is a stateful widget. The problem is when I run the app an error displayed showing a NoSuchMethodError as I tried to call length on null, this means listLokasi is null. So I placed asserts in the query method, in the constructor after the query method, and in the convert method. The results is the assert in the query method did not fire, while the assert in the constructor immediately fired. I have inspected my database helper class and reviewed my code and I cannot find the flaw in my code. Any help in this problem would be appreciated. I shall display the code below.
This is the database helper class.
class DatabaseHelper {
static final _instance = DatabaseHelper._internal();
DatabaseHelper._internal();
factory DatabaseHelper() {
return _instance;
}
Database db;
Future initDatabase() async {
var databasePath = await getDatabasesPath();
var path = join(databasePath, 'table.db');
db = await openDatabase(path, version: 1, onCreate: onCreate);
}
onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE lokasi
(id INTEGER PRIMARY KEY,
name TEXT,
description TEXT,
category TEXT,
latitude REAL,
longitude REAL,
image BLOB)
''');
}
Future<Lokasi> insert(Lokasi lokasi) async {
await db.insert('lokasi', lokasi.toJson());
return lokasi;
}
Future<List<Lokasi>> query() async {
var list = await db.query('lokasi');
return List.generate(list.length, (i) => Lokasi.fromJson(list[i]));
}
}
This is the DataPage class.
class DataPage extends StatefulWidget {
final savedUsername;
const DataPage({this.savedUsername = 'User'});
#override
_DataPageState createState() => _DataPageState();
}
class _DataPageState extends State<DataPage> {
DatabaseHelper db = DatabaseHelper();
List<Lokasi> listLokasi;
List<LokasiConvert> listLokasiConvert;
_DataPageState() {
query();
assert(listLokasi != null);
convert();
}
convert() {
assert(listLokasi != null);
listLokasiConvert = List.generate(
listLokasi.length,
(i) => LokasiConvert(
name: listLokasi[i].name,
description: listLokasi[i].category,
category: listLokasi[i].category,
latitude: listLokasi[i].latitude,
longitude: listLokasi[i].longitude,
image: File.fromRawPath(listLokasi[i].image),
),
);
}
Future<List<Lokasi>> query() async {
listLokasi = await db.query();
assert(listLokasi != null);
return listLokasi;
}
void sendUsername(BuildContext context) {
String username = widget.savedUsername;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MainPage(username: username),
),
);
}
void sendUsernameToChart(BuildContext context) {
String chartUsername = widget.savedUsername;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChartPage(savedUsername: chartUsername),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text('Data'),
actions: [
RaisedButton(
child: Text('Logout'),
onPressed: () {
Navigator.popUntil(context, ModalRoute.withName('/'));
},
),
RaisedButton(
child: Text('Main'),
onPressed: () {
sendUsername(context);
},
),
RaisedButton(
child: Text('Charts'),
onPressed: () {
sendUsernameToChart(context);
},
),
],
),
body: ListView.builder(
itemBuilder: (context, i) {
return Card(
child: Row(
children: [
Image.file(
listLokasiConvert[i].image,
width: 100,
height: 100,
),
Column(
children: [
Text(listLokasiConvert[i].name),
Text(listLokasiConvert[i].category),
Text(listLokasiConvert[i].description),
Text('Coordinates: ' +
listLokasiConvert[i].latitude.toString() +
', ' +
listLokasiConvert[i].longitude.toString()),
],
)
],
));
},
itemCount: listLokasiConvert.length,
),
);
}
}
Once again, thank you for any help given.
Maybe you can try this. Hope its help you.
db.query().then((value) {
setState(() {
listLokasi = value
});
});
The solution is actually simple as I discovered in my process. The list is a future so I am supposed to use a future builder and then wrap the listview builder with the future builder. Let me show my finished code of that specific page.
class DataPage extends StatefulWidget {
final savedUsername;
const DataPage({this.savedUsername = 'User'});
#override
_DataPageState createState() => _DataPageState();
}
class _DataPageState extends State<DataPage> {
DatabaseHelper db = DatabaseHelper();
List<Lokasi> listLokasi;
delete(value) async {
await db.delete(value);
}
Future<List<Lokasi>> query() async {
listLokasi = await db.query();
return listLokasi;
}
void sendUsername(BuildContext context) {
String username = widget.savedUsername;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MainPage(username: username),
),
);
}
void sendUsernameToChart(BuildContext context) {
String chartUsername = widget.savedUsername;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChartPage(savedUsername: chartUsername),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text('Data'),
actions: [
RaisedButton(
child: Text('Logout'),
onPressed: () {
Navigator.popUntil(context, ModalRoute.withName('/'));
},
),
RaisedButton(
child: Text('Main'),
onPressed: () {
sendUsername(context);
},
),
RaisedButton(
child: Text('Charts'),
onPressed: () {
sendUsernameToChart(context);
},
),
],
),
body: FutureBuilder(
future: query(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemBuilder: (context, i) {
return Card(
child: Row(
children: [
Column(
children: [
Text(snapshot.data[i].name),
Text(snapshot.data[i].category),
Text(snapshot.data[i].description),
Text('Coordinates: ' +
snapshot.data[i].latitude.toString() +
', ' +
snapshot.data[i].longitude.toString()),
],
),
Container(
width: 100,
height: 100,
child: Image.memory(snapshot.data[i].image),
),
Container(
width: 30,
height: 30,
child: IconButton(
onPressed: () {
db.delete(snapshot.data[i].name);
setState(() {});
},
icon: Icon(Icons.delete, size: 30),
iconSize: 30,
),
)
],
),
);
},
itemCount: snapshot.data.length,
);
} else {
return Center(child: CircularProgressIndicator());
}
},
),
);
}
}

Flutter Cubit: good way to get stored variable?

I'm using Flutter and Cubit for the first time, and I would like to know if this is a good way to retrieve a stored variable, in my cas the current loggued user.
After loggued in, the user can go to his profile page and see it/update it.
Login form:
submit(BuildContext context) async {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
final authCubit = context.read<AuthCubit>();
authCubit.login(
email: _data.email!,
password: _data.password!,
deviceName: _deviceInfos.deviceName,
);
}
}
AuthCubit: login method:
class AuthCubit extends Cubit<AuthState> {
dynamic user;
Future<void> login({
required String email,
required String password,
required String deviceName,
}) async {
emit(AuthLoading());
// Get the user from the API
this.user = apiResponse['user'];
emit(AuthConnected(user));
}
}
Profile page:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
),
body: BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
final user = context.read<AuthCubit>().user;
return Center(
child: Column(
children: <Widget>[
Text('Hello, ' + (user != null ? user['name'] : 'stranger.')),
ElevatedButton(
onPressed: () {
context.read<AuthCubit>().logout();
},
child: Text('Logoout'),
),
],
),
);
},
),
);
}
Any suggestion/advice is really appreciated. Thanks!
Ok, so what's wrong with your example is that you are not using state at all. User is a part of your state and I see that you are already storing it (emit(AuthConnected(user)) here, so just remove the user object from BLoC:
class AuthCubit extends Cubit<AuthState> {
Future<void> login({
required String email,
required String password,
required String deviceName,
}) async {
emit(AuthLoading());
// Get the user from the API
final user = apiResponse['user'];
emit(AuthConnected(user));
}
}
Then, inside UI, you should get the user from the state object and not from BLoC directly:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
),
body: BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
final user = state.user; // You should get the error here
return Center(
child: Column(
children: <Widget>[
Text('Hello, ' + (user != null ? user['name'] : 'stranger.')),
ElevatedButton(
onPressed: () {
context.read<AuthCubit>().logout();
},
child: Text('Logoout'),
),
],
),
);
},
),
);
}
Now you should get an error since not all of your states contain the user object, e.g. AuthLoading is missing that. To fix that, you should check the state type and render UI accordingly:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
),
body: BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
if (state is AuthLoading) {
return CircularProgressIndicator(); // E.g. show loader
}
final user = state.user;
return Center(
child: Column(
children: <Widget>[
Text('Hello, ' + (user != null ? user['name'] : 'stranger.')),
ElevatedButton(
onPressed: () {
context.read<AuthCubit>().logout();
},
child: Text('Logoout'),
),
],
),
);
},
),
);
}

MultiProvider sending NULL in child widgets but prints right value in Console

My HomePagewhere Providers are initilized:
Widget build(BuildContext context) {
return SafeArea(
child: MultiProvider(
providers: [
ChangeNotifierProvider<EmailAuth>(create: (context) => EmailAuth()),
],
child: Scaffold(
resizeToAvoidBottomInset: true,
floatingActionButton: FloatingActionButton(.....
My Authentication function that is triggered when user logs-in (Firebase)
class EmailAuth extends ChangeNotifier {
final _auth = FirebaseAuth.instance;
final dbRef = FirebaseFirestore.instance.collection("users");
String userid;
Future signIn({String email, String password}) async {
final currentUser = await _auth.signInWithEmailAndPassword(
email: email, password: password);
if (currentUser != null) {
userid = _auth.currentUser.uid;
dbRef.doc(_auth.currentUser.uid).update({
"lastLogin": DateTime.now(),
});
} else {
print("something didn't work");
}
print(userid);
notifyListeners();
return userid;
}
}
This is how my Consumer is setup in the HomePage - AppBar
title: Consumer<EmailAuth>(
builder: (context, data, child) => Text(
"${data.userid}",
style: TextStyle(color: Colors.indigoAccent),
),
),
But the output on AppBar is NULL. What am I doing wrong?!
I have been using this as reference for implementation:
https://medium.com/flutter-community/making-sense-all-of-those-flutter-providers-e842e18f45dd
Something similar was a known error in the older Provider Package. Please update to latest and check if the issue is still there. However,
This is how a MultiProvider should look like:
#override
Widget build(BuildContext context) {
return MultiProvider( // <--- MultiProvider
providers: [
ChangeNotifierProvider<MyModel>(create: (context) => MyModel()),
ChangeNotifierProvider<AnotherModel>(create: (context) => AnotherModel()),
],
And should be consumed like this
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
// We have access to the model.
myModel.doSomething();
},
);
},
)
class MyModel with ChangeNotifier { // <--- MyModel
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
notifyListeners();
}
}