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'),
),
],
),
);
},
),
);
}
Related
enter code hereI want to open a screen to add extra information if it is not set yet. So after the user is logged in I check if the extra info is set. If not I want it to go to a screen to fill in the info. If the user is done it should go to a "Homescreen". If the user info is already set it should immediately go to the home screen.
I already tried to just go to the extra info form and then Navigator.push to the home screen but then it has difficulties with logging out. I searched for a long time but can not find anything.
class CampPage extends StatelessWidget {
final String email;
final String uid;
const CampPage({super.key, required this.email, required this.uid});
#override
Widget build(BuildContext context) {
return FutureBuilder(
// ignore: unrelated_type_equality_checks
future: context.read<UserProvider>().exists(uid) == true
? null
: Future.delayed(Duration.zero, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewUserPage(email: email, userId: uid),
),
);
}),
builder: (context, snapshot) => Scaffold(
drawer: const DrawerHomePage(),
appBar: AppBar(
title: const Text("Camp Page"),
),
body: Column(
children: const [
Text("nieuwe features"),
],
),
),
);
}
}
this is one of the things I try but the NewUserPage always pops up and I only want it to pop up if context.read<UserProvider>().exists(uid) == false
also the solution mentioned does not work for me. I think because there is a screen in between the login and logout (The form screen) the logout function does not work properly.
`
class UserPage extends StatelessWidget {
const UserPage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
child: const Text("Submit"),
onPressed: () {
//Log out of Firestore Authentication
},
),
);
}
}
class NewForm extends StatelessWidget {
const NewForm({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
child: const Text("Submit"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const UserPage()),
);
},
),
);
}
}
Widget build(BuildContext context) {
return FutureBuilder(
future: context.read<UserProvider>().exists(uid)
builder: (context, snapshot) {
if (snapshot.hasdata){
if (snapshot.data == true) {
return const UserPage();
} else {
return const NewForm();
}
}
else // show a proggress bar
}
);
}
`
Does someone still have another solution?
I think you should do this:
Widget build(BuildContext context) {
return FutureBuilder(
future: context.read<UserProvider>().exists(uid)
builder: (context, snapshot) {
if (snapshot.hasdata){
if (snapshot.data == true) // then the user exist
else // the user doesn't exist
}
else // show a proggress bar
}
);
}
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());
}
},
),
);
}
}
I have two screens, one where the user can chat with a particular person and the second screen, where he can see a list of all the chats.
The aim is to display the last message on the second screen. This is done as follows:
The user sends/receives a new message?
Update the database
BloC sends a new stream of data by fetching the newest data.
The problem is, the stream builder isn't listening to the new data (not sure why). To the best of my knowledge, the BloC is sending a new stream of data when the user sends a message, it just doesn't re-render in the list.
Here's a shortened version of the code:
class ChatScreen extends StatelessWidget {
final ContactsBloc _contactsBloc = ContactsBloc();
#override()
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context, true);
},
),
body: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller: messageTextController,
onChanged: (value) {
message = value;
},
decoration: kMessageTextFieldDecoration,
),
),
FlatButton(
onPressed: () async {
//update remote and local databases
await _contactsBloc.updateContact(
{'last_message': utf8.decode(base64.decode(message))},
'conversation_id = ?',
[conversationId]);
},
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
The chats screen:
class ChatsScreen extends StatefulWidget {
static const id = 'chats';
#override
_ChatsScreenState createState() => _ChatsScreenState();
}
class _ChatsScreenState extends State<ChatsScreen> {
final ContactsBloc _contactsBloc = ContactsBloc();
Iterable<Contact> contacts;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Chats'),
body: Container(
child: StreamBuilder(
stream: _contactsBloc.contacts,
builder: (context, results) {
print('New stream: $results');
if (!results.hasData) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: CircularProgressIndicator(),
),
],
);
} else {
List contacts = results.data;
contacts = contacts
.where((element) => element.lastMessage != null)
.toList();
if (contacts.length > 0) {
return ListView.builder(
itemCount: contacts.length,
itemBuilder: (context, index) {
ContactModel contact = contacts[index];
return ChatItem(
name: contact.name,
message: contact.lastMessage,
profilePicture: contact.profilePictureUrl,
lastSeen: contact.lastSeen,
user: currentUser,
toUser: contact.uid,
conversationId: contact.conversationId,
);
},
);
}
return Container();
}
},
)),
);
}
}
The contact BloC:
class ContactsBloc {
ContactsBloc() {
getAllContacts();
}
final _contactsController = StreamController<List<ContactModel>>.broadcast();
Stream<List<ContactModel>> get contacts => _contactsController.stream;
_dispose() {
_contactsController.close();
}
getAllContacts() async {
List<ContactModel> contacts = await DatabaseProvider.db.getAllContacts();
_contactsController.sink.add(contacts);
}
updateContact(var update, String where, List whereArgs) async {
await DatabaseProvider.db.updateContact(update, where, whereArgs);
getAllContacts();
}
}
For now try adding this to create a Singleton instance of ContactBloc
class ContactsBloc{
ContactsBloc._();
static final ContactsBloc _instance = ContactsBloc._();
factory ContactsBloc() => _instance;
/// the rest of your code...
}
I would recommend checking some state management if you want more control of your classes (Bloc, Provider, Redux, etc)
I am making an app in which user requests to book a butcher or make order for some amount of meat. I have two classes.
BookingRequests (through which I see booking requests of all users from booking_collection)
OrderRequests (through which I see order requests of all users from order_collection)
The code of both is almost similar. One displays a ListView of X-Animal slaughtering booked. The other displays X-Animal meat ordered. There is an IconButton in both. I want if I press the icon button either class data should be saved in a third collection approved_requests. I am new to flutter. Can anyone help me in doing so? Here is the code of booking.dart
class BookingRequests extends StatelessWidget {
final db = Firestore.instance;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: db.collection('booking_collection').snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: snapshot.data.documents.map((doc) {
return ListTile(
title: new Text(
"${doc.data['animal']} slaughtering booked",
),
trailing: IconButton(
icon: Icon(Icons.done),
onPressed: (){
//implementation here
},
),
);
}).toList(),
);
} else {
return SizedBox();
}
}),
],
),
);
}
}
Similarly my order.dart
class OrderRequests extends StatelessWidget {
final db = Firestore.instance;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: db.collection('order_collection').snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: snapshot.data.documents.map((doc) {
return ListTile(
title: new Text(
"${doc.data['animal']} meat ordered",
),
trailing: IconButton(
icon: Icon(Icons.done),
onPressed: (){
//implementation here
},
),
);
}).toList(),
);
} else {
return SizedBox();
}
}),
],
),
);
}
}
Future<bool> show(BuildContext context) async {
return Platform.isIOS
? await showCupertinoDialog<bool>
(context: context, builder: (context)=>this)
:await showDialog<bool>(
context: context,
builder: (context) => this,
);
}
Can anyone help me to understand the term 'this',what does 'this' refer to and how does showDialog works that it returns Future.I tried to read documentation but still couldn't understand it?Is it the same as AlertDialog widget?
well, it's pretty much what the documentation said, it shows a material dialog above the current content of your app, as for this it passes the current widget as child for the dialog, as for the returned value is just like normal page navigation that when you call pop(context, {value}) method you can also return a value, so that value that inside pop will be returned from the dialog.
here is an example below:
class DialogTest extends StatefulWidget {
#override
_DialogTestState createState() => _DialogTestState();
}
class _DialogTestState extends State<DialogTest> {
// the value that will be typed to the dialog
String dialogText;
// the value that will be returned from the dialog
String returnedFromDialog;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample Code'),
),
body: Center(
child:
Text('You got this value from the dialog => $returnedFromDialog'),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
returnedFromDialog = await showDialog<String>(
context: context,
builder: (context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
onChanged: (value) => dialogText = value,
),
FlatButton(
onPressed: () {
setState(() => Navigator.pop(context, dialogText));
},
child: Text(
'Close dialog',
style: TextStyle(color: Colors.red),
),
)
],
),
);
});
},
child: Icon(Icons.open_in_browser),
),
);
}
}