I need to print a variable from another class - flutter

hi everyone I need to print a variables from another class which are they bannerImageUrl and the profileImageUrl I saved them to the firebase storage and it is appears to the folders there but I want to print their url in one of the collection in the firebasefirestore .
this the UtilsService to upload the images to database
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart' as firebase_storage;
class UtilsService {
Future<String> uploadFile(File _image, String path) async {
// refrence to upload an img
firebase_storage.Reference storageReference =
firebase_storage.FirebaseStorage.instance.ref(path);
firebase_storage.UploadTask uploadTask = storageReference.putFile(_image);
await uploadTask.whenComplete(() => null);
String returnURL = '';
await storageReference.getDownloadURL().then((fileURL) {
returnURL = fileURL;
});
return returnURL;
}
}
and this the UserService to save the images into the data base
import 'dart:collection';
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:insight_software/models/user.dart';
import 'package:insight_software/other_screen/UtlsService.dart';
class UserService {
UtilsService _utilsService = UtilsService();
List<UserModel?> _userListFromQuerySnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((doc) {
return UserModel(
id: doc.id,
name: doc.get("First Name:"),
profileImageUrl: doc.get('profileImageUrl') ?? '',
bannerImageUrl: doc.get('bannerImageUrl') ?? '',
email: doc.get('email') ?? '',
);
}).toList();
}
UserModel? _userFromFirebaseSnapshot(DocumentSnapshot snapshot) {
return snapshot != null
? UserModel(
id: snapshot.id,
name: snapshot.get("First Name:"),
profileImageUrl: snapshot.get('profileImageUrl'),
bannerImageUrl: snapshot.get('bannerImageUrl'),
email: snapshot.get('email'),
)
: null;
}
Stream<UserModel?> getUserInfo(uid) {
return FirebaseFirestore.instance
.collection("Users")
.doc(uid)
.snapshots()
.map(_userFromFirebaseSnapshot);
}
// save the images into the database
Future<void> updateprofile(
File _bannerImage, File _profileImage, String name) async {
// track for the banner image and the profile image
String bannerImageUrl = '';
String profileImageUrl = '';
if (_bannerImage != null) {
bannerImageUrl = await _utilsService.uploadFile(_bannerImage,
'Users/profile/${FirebaseAuth.instance.currentUser!.uid}/banner');
}
if (_profileImage != null) {
profileImageUrl = await _utilsService.uploadFile(_profileImage,
'Users/profile/${FirebaseAuth.instance.currentUser!.uid}/profileimg');
}
// UPLOADING THE BANNER IMAGE AND PROFILE IMAGE AND THE NAME TO DATABASE
Map<String, Object> data = new HashMap();
if (name != '') data['First Name'] = name;
if (bannerImageUrl != '') data['bannerImageUrl'] = bannerImageUrl;
if (profileImageUrl != '') data['profileImageUrl'] = profileImageUrl;
await FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.update(data);
}
}
and the class that I want to print the variables URL is SignUppage2 it is too long but I will provide the part that I want to call the variables in
child: MaterialButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
Users.add({
'First Name': Fname,
'Last Name': Lname,
'user role ': value,
'user gender': value2,
'Nationality': value3,
'intersted Displiance': value4,
'intersted university': value5,
'email': _signupscr!.auth.currentUser?.email,
'bannerImageUrl': _userModel.bannerImageUrl,
'profileImageUrl': _userModel.profileImageUrl,
}).then((value) => print('user added')).catchError(
(error) => print('failed to add user:$error'));
Navigator.of(context).pushNamed('sign up new account');
} else {
print("sign up failed");
}
},
how I can print the bannerImageURL and the profileImageurl in the SignUppage2??
what i add in the class it is print an empty string in the Firestore

You can use argument parameter to pass data to another screen.
Navigator.pushNamed(context, "sign up new account",arguments: {"bannerImageURL " :
_userModel.bannerImageUrl, "profileImageUrl": _userModel.profileImageUrl});
},
And on SignUpPage2 you can get data in build method get as:
#override
Widget build(BuildContext context) {
final Map<String, Object>rcvdData = ModalRoute.of(context).settings.arguments;
print("rcvd fdata ${rcvdData['profileImageUrl']}");
print("rcvd fdata ${rcvdData['bannerImageURL ']}");
return Scaffold(appBar: AppBar(title: Text("Second")),
body: Container(child: Column(children: <Widget>[
Text("Second"),
],),),);
}

Related

I'm making a note app and the NotesView clears all other notes and replaces it with my most recent note

This is the NotesView:
import 'package:app_two/lib/constants/routes.dart';
import 'package:app_two/lib/enums/menu_action.dart';
import 'package:app_two/lib/services/auth/auth_service.dart';
import 'package:app_two/lib/services/auth/crud/notes_service.dart';
import 'package:flutter/material.dart';
class NotesView extends StatefulWidget {
const NotesView({super.key});
#override
State<NotesView> createState() => _NotesViewState();
}
class _NotesViewState extends State<NotesView> {
late Future _myFuture;
late final NotesService _notesService;
String get userEmail => AuthService.firebase().currentUser!.email!;
#override
void initState() {
super.initState();
_notesService = NotesService();
_myFuture = getGet();
}
// #override //delete
// void dispose() {
// _notesService.close();
// super.dispose();
// }
getGet() async {
return await _notesService.getOrCreateUser(email: userEmail);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Your Notes'),
actions: [
IconButton(
onPressed: () {
Navigator.of(context).pushNamed(newNoteRoute);
},
icon: const Icon(Icons.add),
),
PopupMenuButton<MenuAction>(
onSelected: (value) async {
switch (value) {
case MenuAction.logout:
final shouldLogout = await showLogOutDialog(context);
if (shouldLogout) {
await AuthService.firebase().logOut();
Navigator.of(context).pushNamedAndRemoveUntil(
loginRoute,
(_) => false,
);
}
}
},
itemBuilder: (context) {
return const [
PopupMenuItem<MenuAction>(
value: MenuAction.logout,
child: Text('Logout'),
),
];
},
)
],
),
body: FutureBuilder(
future: _myFuture,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
return StreamBuilder(
stream: _notesService.allNotes,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
case ConnectionState.active:
if (snapshot.hasData) {
final allNotes = snapshot.data as List<DatabaseNote>;
return ListView.builder(
itemCount: allNotes.length,
itemBuilder: (context, index) {
final note = allNotes[index];
return ListTile(
title: Text(
note.text,
maxLines: 1,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
);
},
);
} else {
return const CircularProgressIndicator();
}
default:
return const CircularProgressIndicator();
}
},
);
default:
return const CircularProgressIndicator();
}
},
),
);
}
Future<bool> showLogOutDialog(BuildContext context) {
return showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Sign out'),
content: const Text('Are you sure you want to sign out?'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: const Text('Cancel')),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: const Text('Logout'))
],
);
},
).then((value) => value ?? false);
}
}
//Notes_service
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' show join;
import 'crud_exceptions.dart';
class NotesService {
Database? _db;
List<DatabaseNote> _notes = [];
static final NotesService _shared = NotesService._sharedInstance();
NotesService._sharedInstance() {
_notesStreamController = StreamController<List<DatabaseNote>>.broadcast(
onListen: () {
_notesStreamController.sink.add(_notes);
//populate stream with a stream of notes that we've already read from the database
},
);
}
factory NotesService() => _shared; //singleton
late final StreamController<List<DatabaseNote>> _notesStreamController;
Stream<List<DatabaseNote>> get allNotes => _notesStreamController.stream;
Future<DatabaseUser?> getOrCreateUser({required String email}) async {
try {
final user = await getUser(email: email);
return user;
} on CouldNotFindUser {
final createdUser = await createUser(email: email);
return createdUser;
} on DatabaseIsNotOpen {
open();
} catch (e) {
print(e.toString());
rethrow;
}
}
Future<void> _cacheNotes() async {
final allNotes = await getAllNotes();
_notes = allNotes.toList();
_notesStreamController.add(_notes);
}
Future<DatabaseNote> updateNote({
required DatabaseNote note,
required String text,
}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
//make sure note exists
await getNote(id: note.id);
//update db
final updatesCount = await db.update(noteTable, {
textColumn: text,
isSyncedwithCloudColumn: 0,
});
if (updatesCount == 0) {
throw CouldNotUpdateNote();
} else {
final updatedNote = await getNote(id: note.id);
_notes.removeWhere((note) => note.id == updatedNote.id);
_notes.add(updatedNote);
_notesStreamController.add(_notes);
return updatedNote;
}
}
Future<Iterable<DatabaseNote>> getAllNotes() async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
final notes = await db.query(noteTable);
return notes.map((noteRow) => DatabaseNote.fromRow(noteRow));
}
Future<DatabaseNote> getNote({required int id}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
final notes = await db.query(
noteTable,
limit: 1,
where: 'id = ?',
whereArgs: [id],
);
if (notes.isEmpty) {
throw CouldNotFindNote();
} else {
final note = DatabaseNote.fromRow(notes.first);
_notes.removeWhere((note) => note.id == id);
_notesStreamController.add(_notes);
return note;
}
}
Future<int> deleteAllNotes() async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
final numberOfdeletions = await db.delete(noteTable);
_notes = [];
_notesStreamController.add(_notes);
return numberOfdeletions;
}
Future<void> deleteNote({required int id}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
final deletedCount = await db.delete(
noteTable,
where: 'id = ?',
whereArgs: [id],
);
if (deletedCount == 0) {
throw CouldNotDeleteNote();
} else {
_notes.removeWhere((note) => note.id == id);
_notesStreamController.add(_notes);
}
}
Future<DatabaseNote> createNote({required DatabaseUser owner}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
//Make sure owner exists in database withthe correct id
final dbUser = await getUser(email: owner.email);
if (dbUser != owner) {
throw CouldNotFindUser();
}
const text = '';
//create the note
final noteId = await db.insert(noteTable, {
userIdColumn: owner.id,
textColumn: text,
isSyncedwithCloudColumn: 1,
});
final note = DatabaseNote(
id: noteId,
userId: owner.id,
text: text,
isSyncedWithCloud: true,
);
_notes.add(note);
_notesStreamController.add(_notes);
return note;
}
Future<DatabaseUser> getUser({required String email}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
final results = await db.query(
userTable,
limit: 1,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (results.isEmpty) {
throw CouldNotFindUser();
} else {
return DatabaseUser.fromRow(results.first);
}
}
Future<DatabaseUser> createUser({required String email}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
final results = await db.query(
userTable,
limit: 1,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (results.isNotEmpty) {
throw UserAlreadyExists();
}
final userId = await db.insert(userTable, {
emailColumn: email.toLowerCase(),
});
return DatabaseUser(id: userId, email: email);
}
Future<void> deleteUser({required String email}) async {
final db = _getDatabaseorThrow();
final deletedCount = await db.delete(
userTable,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (deletedCount != 1) {
throw CouldNotDeleteUser();
}
}
Database _getDatabaseorThrow() {
final db = _db;
if (db == null) {
throw DatabaseIsNotOpen();
} else {
return db;
}
}
Future<void> close() async {
final db = _db;
if (db == null) {
try {
await open();
} on DatabaseIsNotOpen {
open();
}
// throw DatabaseIsNotOpen();
} else {
await db.close();
_db = null;
}
}
Future<void> _ensureDbIsOpen() async {
if (_db != null) {
try {
await open();
} on DatabaseAlreadyOpenException {
//empty
}
}
}
Future<void> open() async {
if (_db != null) {
throw DatabaseAlreadyOpenException();
}
try {
await _ensureDbIsOpen();
final docsPath = await getApplicationDocumentsDirectory();
final dbPath = join(docsPath.path, dbName);
final db = await openDatabase(dbPath);
_db = db;
//create user table
await db.execute(createUserTable);
//create note table
await db.execute(createNoteTable);
await _cacheNotes();
} on MissingPlatformDirectoryException {
throw UnableToGetDocumentDirectory();
}
}
}
#immutable
class DatabaseUser {
final int id;
final String email;
const DatabaseUser({
required this.id,
required this.email,
});
DatabaseUser.fromRow(Map<String, Object?> map)
: id = map[idColumn] as int,
email = map[emailColumn] as String;
#override
String toString() => 'Person, ID = $id, email= $email';
#override
bool operator ==(covariant DatabaseUser other) => id == other.id;
#override
int get hashCode => id.hashCode;
}
class DatabaseNote {
final int id;
final int userId;
final String text;
final bool isSyncedWithCloud;
DatabaseNote({
required this.id,
required this.userId,
required this.text,
required this.isSyncedWithCloud,
});
DatabaseNote.fromRow(Map<String, Object?> map)
: id = map[idColumn] as int,
userId = map[userIdColumn] as int,
text = map[textColumn] as String,
isSyncedWithCloud =
(map[isSyncedwithCloudColumn] as int) == 1 ? true : false;
#override
String toString() =>
'Note, ID = $id, userId = $userId, isSyncedWithCloud = $isSyncedWithCloud, text = $text';
#override
bool operator ==(covariant DatabaseNote other) => id == other.id;
#override
int get hashCode => id.hashCode;
}
const dbName = 'notes.db';
const noteTable = 'note';
const userTable = 'user';
const idColumn = 'id';
const emailColumn = 'email';
const userIdColumn = 'user_id';
const textColumn = 'text';
const isSyncedwithCloudColumn = 'is_synced_with_cloud';
const createUserTable = '''CREATE TABLE IF NOT EXISTS "user" (
"id" INTEGER NOT NULL,
"email" TEXT NOT NULL UNIQUE,
PRIMARY KEY("id" AUTOINCREMENT)
);''';
const createNoteTable = '''CREATE TABLE IF NOT EXISTS "note" (
"id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"text" TEXT,
"is_synced_with_cloud" INTEGER DEFAULT 0,
FOREIGN KEY("user_id") REFERENCES "user"("id"),
PRIMARY KEY("id" AUTOINCREMENT)
);''';
I'm making a note app and the NotesView clears all other notes and replaces it with my most recent not. I have no idea why this is happening
Each note you input should display on a tile. But instead this happens:
But only after I restart the app. In the next images, I add a new note and then restart the application
I believe I did something very in my StreamBuilder but don't know what.
I'd appreciate any assistance.
Try changing the case ConnectionState.active to case ConnectionState.done in your StreamBuilder.
Duplication may be occurring due to building your list inside case ConnectionState.active.
I don't know what your db.update does or your SQL query looks like but if you don't pass the id how are you supposed to update the note?
Future<DatabaseNote> updateNote({
required DatabaseNote note,
required String text,
}) async {
await _ensureDbIsOpen();
final db = _getDatabaseorThrow();
//make sure note exists
await getNote(id: note.id);
//update db
final updatesCount = await db.update(noteTable, { ///how does magically know what note to update?
textColumn: text,
isSyncedwithCloudColumn: 0,
});
if (updatesCount == 0) {
throw CouldNotUpdateNote();
} else {
final updatedNote = await getNote(id: note.id);
_notes.removeWhere((note) => note.id == updatedNote.id);
_notes.add(updatedNote);
_notesStreamController.add(_notes);
return updatedNote;
}
}
If you're using INSERT OR REPLACE INTO because you don't pass the id you're creating a new row instead of updating it and your cache list is giving you false information because at the end you think you update it correctly and just replace the note with the same id, your cache list doesn't reflect the real information anymore.
If you're updating all notes in your table because the where clause doesn't have an id you will present your current problem when restarting
Check your db layer apart to see if you're actually doing what you want.

Repository testing with mocktail and flutter test - I'm trying to write a test for my repository but I keep getting an error 'No such mrthod'

error message Repository testing with mocktail and flutter test - I'm trying to write a test for my repository but I keep getting an error 'No such method'
I've tried with ThenAnswer as well but it still won't go through. I'll appreciate any help :)
That's what my code looks like :
Test file:
`import 'package:curlzzz_new/data_source/remote_watch_data_source.dart';
import 'package:curlzzz_new/models/watch_model.dart';
import 'package:curlzzz_new/repositories/watch_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockWatchDataSource extends Mock implements RemoteWatchDataSource {}
void main() {
//sut to System under testing
late WatchRepository sut;
late MockWatchDataSource dataSource;
setUp(() {
dataSource = MockWatchDataSource();
sut = WatchRepository(dataSource);
});
group('getWatchStream', () {
test('should return a stream of WatchModels', () {
//1
when(() => dataSource.getRemoteWatchStream()).thenThrow(
(_) => Stream.value([
WatchModel(title: 'titanic', id: '555'),
WatchModel(title: 'top gun', id: '555'),
WatchModel(title: 'matrix', id: '111')
]),
);
//2
final results = sut.getWatchStream();
//3
expect(results, [
WatchModel(title: 'titanic', id: '555'),
WatchModel(title: 'top gun', id: '555'),
WatchModel(title: 'matrix', id: '111')
]);
});
});
}
`
Repository:
import 'package:curlzzz_new/data_source/remote_watch_data_source.dart';
import 'package:curlzzz_new/models/watch_model.dart';
class WatchRepository {
WatchRepository(this.remoteDataSource);
final RemoteWatchDataSource remoteDataSource;
Stream<List<WatchModel>> getWatchStream() {
return remoteDataSource.getRemoteWatchStream().map((querySnapshot) {
return querySnapshot.docs.map((doc) {
return WatchModel(title: doc['title'], id: doc.id);
}).toList();
});
}
Future<void> addMovies({
required String title,
}) {
return remoteDataSource.addRemoteWatchData(title: title);
}
Future<void> dismiss({required String id}) {
return remoteDataSource.dismissRemoteData(id: id);
}
}
Data Source:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
class RemoteWatchDataSource {
Stream<QuerySnapshot<Map<String, dynamic>>> getRemoteWatchStream() {
final userID = FirebaseAuth.instance.currentUser?.uid;
if (userID == null) {
throw Exception('User is not logged in');
}
return FirebaseFirestore.instance
.collection('users')
.doc(userID)
.collection('movies')
.snapshots();
}
Future<DocumentReference<Map<String, dynamic>>?> addRemoteWatchData({
required String title,
}) async {
final userID = FirebaseAuth.instance.currentUser?.uid;
if (userID == null) {
throw Exception('User is not logged in');
}
return FirebaseFirestore.instance
.collection('users')
.doc(userID)
.collection('movies')
.add(
{'title': title},
);
}
Future<void> dismissRemoteData({required String id}) async {
final userID = FirebaseAuth.instance.currentUser?.uid;
if (userID == null) {
throw Exception('User is not logged in');
}
return FirebaseFirestore.instance
.collection('users')
.doc(userID)
.collection(
'movies',
)
.doc(id)
.delete();
}
}
It seems like a a problem with the closure type.
try this. don't combine 2 functions in a single test.
group('getWatchStream', () {
test('should return a stream of WatchModels', () async {
final List<WatchModel> models = [
WatchModel(title: 'titanic', id: '555'),
WatchModel(title: 'top gun', id: '555'),
WatchModel(title: 'matrix', id: '111')
];
//1
when(() => dataSource.getRemoteWatchStream()).thenAnswer(
(_) => Stream.value(models),
);
//2
await dataSource.getRemoteWatchStream();
//3
expect(dataSource.getRemoteWatchStream, emits(models) );
});
});

Refresh page if data isn't shown on screen

I have a Future in my initState function that gets jwt from cache and uses it to get the logged in user's details. The initState function is:
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () async {
final token = await CacheService().readCache(key: "jwt");
if (token != null) {
await Provider.of<ProfileNotifier>(context, listen: false)
.decodeUserData(
context: context,
token: token,
option: 'home',
);
}
});
}
Now, it does work and I do get the data, but not on the first run. I have to either hot reload the emulator or navigate to another page and come back for the page to rebuild itself and show the data on screen. I don't understand why it doesn't show the data on the first run itself.
ProfileNotifier class:
class ProfileNotifier extends ChangeNotifier {
final ProfileAPI _profileAPI = ProfileAPI();
final CacheService _cacheService = CacheService();
ProfileModel _profile = ProfileModel(
profileImage: "",
profileName: "",
profileBio: "",
);
AccountModel _account = AccountModel(
userId: "",
userEmail: "",
userPassword: "",
);
ProfileModel get profile => _profile;
AccountModel get account => _account;
Future decodeUserData({
required BuildContext context,
required String token,
required String option,
}) async {
try {
_profileAPI.decodeUserData(token: token).then((value) async {
final Map<String, dynamic> parsedData = await jsonDecode(value);
var userData = parsedData['data'];
if (userData != null) {
List<String>? userProfileData = await _cacheService.readProfileCache(
key: userData['userData']['id'],
);
if (userProfileData == null) {
final isProfileAvailable =
await Provider.of<ProfileNotifier>(context, listen: false)
.getProfile(
context: context,
userEmail: userData['userData']['userEmail'],
);
if (isProfileAvailable is ProfileModel) {
_profile = isProfileAvailable;
} else {
_account = AccountModel(
userId: userData['userData']['id'],
userEmail: userData['userData']['userEmail'],
userPassword: userData['userData']['userPassword'],
);
_profile = ProfileModel(
profileImage: '',
profileName: '',
);
}
if (option != 'profileCreation' && isProfileAvailable == false) {
Navigator.of(context).pushReplacementNamed(ProfileCreationRoute);
}
} else {
_account = AccountModel(
userId: userData['userData']['id'],
userEmail: userData['userData']['userEmail'],
userPassword: userData['userData']['userPassword'],
);
_profile = ProfileModel(
profileName: userProfileData[3],
profileImage: userProfileData[4],
profileBio: userProfileData[5],
);
}
} else {
Navigator.of(context).pushReplacementNamed(AuthRoute);
}
notifyListeners();
});
} catch (e) {
debugPrint('account/profileNotifier decode error: ' + e.toString());
}
}
Future getProfile({
required BuildContext context,
required String userEmail,
}) async {
try {
var getProfileData = await _profileAPI.getProfile(
userEmail: userEmail,
);
final Map<String, dynamic> parsedProfileData =
await jsonDecode(getProfileData);
bool isReceived = parsedProfileData["received"];
dynamic profileData = parsedProfileData["data"];
if (isReceived && profileData != 'Fill some info') {
Map<String, dynamic> data = {
'id': (profileData['account']['id']).toString(),
'userEmail': profileData['account']['userEmail'],
'userPassword': profileData['account']['userPassword'],
'profile': {
'profileName': profileData['profileName'],
'profileImage': profileData['profileImage'],
'profileBio': profileData['profileBio'],
}
};
AccountModel accountModel = AccountModel.fromJson(
map: data,
);
return accountModel;
} else {
return false;
}
} catch (e) {
debugPrint('profileNotifier getProfile error: ' + e.toString());
}
}
Future setProfile({
required String profileName,
required String profileImage,
required String profileBio,
}) async {
_profile.profileName = profileName;
_profile.profileImage = profileImage;
_profile.profileBio = profileBio;
await _cacheService.writeProfileCache(
key: _account.userId,
value: [
_account.userId,
_account.userEmail,
_account.userPassword as String,
profileName,
profileImage,
profileBio,
],
);
notifyListeners();
}
}
CacheService class:
class CacheService {
Future<String?> readCache({
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
String? cache = await sharedPreferences.getString(key);
return cache;
}
Future<List<String>?> readProfileCache({
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
List<String>? cachedData = await sharedPreferences.getStringList(key);
return cachedData;
}
Future writeCache({required String key, required String value}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.setString(key, value);
}
Future writeProfileCache(
{required String key, required List<String> value}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.setStringList(key, value);
}
Future deleteCache({
required BuildContext context,
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.remove(key).whenComplete(() {
Navigator.of(context).pushReplacementNamed(AuthRoute);
});
}
}
I can't seem to figure out the problem here. Please help.
EDIT: The data is used to show profileImage of user in CircleAvatar like this:
#override
Widget build(BuildContext context) {
ProfileModel profile =
Provider.of<ProfileNotifier>(context, listen: false).profile;
return GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Scaffold(
drawer: const ProfileDrawer(),
appBar: AppBar(
backgroundColor: Colors.white,
leading: Row(children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 9),
child: Builder(builder: (BuildContext context) {
return InkWell(
onTap: () => Scaffold.of(context).openDrawer(),
child: CircleAvatar(
maxRadius: 20.0,
backgroundImage: profile.profileImage.isNotEmpty
? NetworkImage(profile.profileImage)
: null,
child: profile.profileImage.isEmpty
? SvgPicture.asset(
'assets/images/profile-default.svg')
: null),
);
}),
), ....
This CircleAvatar in the appBar shows the image only after the page is rebuilt. There's nothing else on the page except the appbar for now.
When we use ChangeNotifier, it provides two options to access the data. These are:
Read the data - You read the data, it doesn't act as a Stream or State and only one time. This is what you're doing in your case.
Advantage - Whenever the data is needed only one time, for example - Mathematical calculation, you use this.
Disadvantage - It doesn't listen to the changes and the data returned is static.
Watch the data - What you need. It provides the data in a state manner, wherever you access the data using Watch, it (or the widget in the data is used) will be updated whenever the underlying data is updated, even from other Screens/Widgets.
Advantage - The data result is dynamic and the widget is updated whenever the data is updated.
Disadvantage - In case where static data works, it is unnecessary plus it may affect any operations dependent on the data.
There are two ways to use Read and Watch.
The normal functions provided by the Author of the package
//For reading the data
var yourData = Provider.of<YourNotifier>(context, listen: false);
//For watching the data
var yourData = Provider.of<ProfileNotifier>(context, listen: true);
The extension functions on BuildContext provided by the Author:
//For reading the data
var yourData = context.read<YourNotifier>();
//For watching the data
var yourData = context.watch<YourNotifier>();
So, what you need to do is:
Change
ProfileModel profile =
Provider.of<ProfileNotifier>(context, listen: false).profile;
to
ProfileModel profile =
Provider.of<ProfileNotifier>(context, listen: true).profile;
//Or
ProfileModel profile = context.watch<ProfileNotifier>().profile;
Edit: Also, considering good UX, you can use a bool flag to update the UI whenever the data is loaded and if it's loading, show a CircularProgressIndicator.

Flutter - Refresh page if data isn't loaded on screen

I have a Future.delayed in my initState function that gets jwt cache and uses it to get the logged in user's details. The initState function is:
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () async {
final token = await CacheService().readCache(key: "jwt");
if (token != null) {
await Provider.of<ProfileNotifier>(context, listen: false)
.decodeUserData(
context: context,
token: token,
option: 'home',
);
}
});
}
Now it does work and I do get the data but not on the first run. I have to either hot reload the emulator or navigate to another page and come back for the page to rebuild itself and show the data on screen. I don't understand why it doesn't show the data on the first run itself.
I tried to add conditional to build method to run setState and initState again if data is not there.
#override
Widget build(BuildContext context) {
ProfileModel profile =
Provider.of<ProfileNotifier>(context, listen: false).profile;
if (profile.profileName.isEmpty) {
print('reloading to get data');
initState();
setState(() {});
} ....
And it doesn't run coz the data is there but somehow it doesn't show up on the screen till the page is refreshed. I can't seem to figure out the problem here. Please help.
EDIT: ProfileNotifier class:
class ProfileNotifier extends ChangeNotifier {
final ProfileAPI _profileAPI = ProfileAPI();
final CacheService _cacheService = CacheService();
ProfileModel _profile = ProfileModel(
profileImage: "",
profileName: "",
profileBio: "",
);
AccountModel _account = AccountModel(
userId: "",
userEmail: "",
userPassword: "",
);
ProfileModel get profile => _profile;
AccountModel get account => _account;
Future decodeUserData({
required BuildContext context,
required String token,
required String option,
}) async {
try {
_profileAPI.decodeUserData(token: token).then((value) async {
final Map<String, dynamic> parsedData = await jsonDecode(value);
var userData = parsedData['data'];
if (userData != null) {
List<String>? userProfileData = await _cacheService.readProfileCache(
key: userData['userData']['id'],
);
if (userProfileData == null) {
final isProfileAvailable =
await Provider.of<ProfileNotifier>(context, listen: false)
.getProfile(
context: context,
userEmail: userData['userData']['userEmail'],
);
if (isProfileAvailable is ProfileModel) {
_profile = isProfileAvailable;
} else {
_account = AccountModel(
userId: userData['userData']['id'],
userEmail: userData['userData']['userEmail'],
userPassword: userData['userData']['userPassword'],
);
_profile = ProfileModel(
profileImage: '',
profileName: '',
);
}
if (option != 'profileCreation' && isProfileAvailable == false) {
Navigator.of(context).pushReplacementNamed(ProfileCreationRoute);
}
} else {
_account = AccountModel(
userId: userData['userData']['id'],
userEmail: userData['userData']['userEmail'],
userPassword: userData['userData']['userPassword'],
);
_profile = ProfileModel(
profileName: userProfileData[3],
profileImage: userProfileData[4],
profileBio: userProfileData[5],
);
}
} else {
Navigator.of(context).pushReplacementNamed(AuthRoute);
}
notifyListeners();
});
} catch (e) {
debugPrint('account/profileNotifier decode error: ' + e.toString());
}
}
Future getProfile({
required BuildContext context,
required String userEmail,
}) async {
try {
var getProfileData = await _profileAPI.getProfile(
userEmail: userEmail,
);
final Map<String, dynamic> parsedProfileData =
await jsonDecode(getProfileData);
bool isReceived = parsedProfileData["received"];
dynamic profileData = parsedProfileData["data"];
if (isReceived && profileData != 'Fill some info') {
Map<String, dynamic> data = {
'id': (profileData['account']['id']).toString(),
'userEmail': profileData['account']['userEmail'],
'userPassword': profileData['account']['userPassword'],
'profile': {
'profileName': profileData['profileName'],
'profileImage': profileData['profileImage'],
'profileBio': profileData['profileBio'],
}
};
AccountModel accountModel = AccountModel.fromJson(
map: data,
);
return accountModel;
} else {
return false;
}
} catch (e) {
debugPrint('profileNotifier getProfile error: ' + e.toString());
}
}
Future setProfile({
required String profileName,
required String profileImage,
required String profileBio,
}) async {
_profile.profileName = profileName;
_profile.profileImage = profileImage;
_profile.profileBio = profileBio;
await _cacheService.writeProfileCache(
key: _account.userId,
value: [
_account.userId,
_account.userEmail,
_account.userPassword as String,
profileName,
profileImage,
profileBio,
],
);
notifyListeners();
}
}
I've removed the create profile, update profile and profile image upload methods in the notifier as they are not involved here.
The CacheService class using shared_preferences package is:
class CacheService {
Future<String?> readCache({
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
String? cache = await sharedPreferences.getString(key);
return cache;
}
Future<List<String>?> readProfileCache({
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
List<String>? cachedData = await sharedPreferences.getStringList(key);
return cachedData;
}
Future writeCache({required String key, required String value}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.setString(key, value);
}
Future writeProfileCache(
{required String key, required List<String> value}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.setStringList(key, value);
}
Future deleteCache({
required BuildContext context,
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.remove(key).whenComplete(() {
Navigator.of(context).pushReplacementNamed(AuthRoute);
});
}
}
I'm not sure if the code is completely optimized. Any improvement is welcome. Thanks.

Keeping variable value after closing the application but having trouble with database

I'm trying to import device Contacts and list them with checkboxes then user selects and send them to other screen and do action with them.
I have used contacts_service plugin and use following code on init
var selectedContacts = List<Contact>();
List<Contact> _contacts;
Future<void> refreshContacts() async {
var contacts = (await ContactsService.getContacts(
withThumbnails: false, iOSLocalizedLabels: true))
.toList();
setState(() {
_contacts = contacts;
});
}
And list view:
_contacts != null
? ListView.builder(
itemCount: _contacts?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
Contact c = _contacts?.elementAt(index);
return CheckboxListTile(
value: selectedContacts.contains(c),
onChanged: (bool value) {
setState(() {
value
? selectedContacts.add(c)//
: selectedContacts.remove(c);//
});
},
title: Text(c.displayName ?? ""),
secondary: CircleAvatar(child: Text(c.initials())),
);
},
)
: Center(
child: CircularProgressIndicator(),
),
My problem is when app closes and reopens checkboxes are gone and selectedContacts is empty. I have spent many hours trying to get database work but i couldn't make it work mainly because 'Contact' class has phone variable as iterable and i couldn't save it to database.
I have created class on my own that has id, name, phone so it would be much easier to assign them to Database.
But then i couldn't work around value: selectedContacts.contains(c), and checkboxes wont work. I tried to create my own containst function where i take Contact c and its phone value and compare it to my list and find if phones match and send true and false still doesnt work.
My idea is var selectedContacts = here i can do database empty ? List<Contact> : import Contacts from database and after that i think i still don't know how to send that selectedContacts to another file and another main page but i can find it i assume.
I have not tried sharedpref because from what i have read it can store small size and if my User chooses many objects from 'Contact' class it will be a problem.
I am sorry if my explanation is bad .Its not that important i just want to learn, thanks in advance
UPDATE: I have tried to use Database again my code looks like this.
database.dart
import 'dart:io' as io;
import 'package:contacts_service/contacts_service.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
class DatabaseHelper{
static final DatabaseHelper _instance = new DatabaseHelper.internal();
factory DatabaseHelper() => _instance;
static Database _db;
DatabaseHelper.internal();
static initDb() async{
io.Directory documentDirectory = await getApplicationDocumentsDirectory();
String path = join(documentDirectory.path,"contactDatabase.db");
var taskDb= await openDatabase(path,version: 1,onCreate: _onCreate);
return taskDb;
}
static void _onCreate(Database db,int version) async{
await db.execute(
"CREATE TABLE contacts(identifier TEXT PRIMARY KEY, displayName TEXT, givenName TEXT, middleName TEXT, familyName TEXT, prefix TEXT, suffix TEXT, company TEXT, jobTitle TEXT, androidAccountType TEXT, androidAccountName TEXT, emails TEXT, phones TEXT, postalAddresses TEXT, avatar TEXT, birthday TEXT, )"
);
}
static Future<void> addContact(Contact c) async{
final Database db = initDb();
await db.insert('contacts', c.toMap(),conflictAlgorithm: ConflictAlgorithm.replace,);
}
static Future<void> deleteContact(Contact c) async{
final Database db = initDb();
await db.delete('contacts', where: "id = ?", whereArgs: [c.identifier],);
}
static Future<List<Contact>> importContacts() async{
final Database db = initDb();
final List<Map<String, dynamic>> maps = await db.query('contacts');
return List.generate(maps.length, (index) => Contact.fromMap(maps[index]));
}
static Future<bool> dbEmpty() async{
final Database db = initDb();
int count = Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM contacts'));
if (count>0){
return true;
}
else
return false;
}
}
And my contact screen looks like this.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:contacts_service/contacts_service.dart';
import 'database.dart';
class ContactScreen extends StatefulWidget {
#override
_ContactScreenState createState() => _ContactScreenState();
}
class _ContactScreenState extends State<ContactScreen> {
void checkDb() async{
List<Contact> selectedContacts = await DatabaseHelper.dbEmpty()?DatabaseHelper.importContacts():List<Contact>();
}
List<Contact> selectedContacts;
List<Contact> _contacts;
DatabaseHelper databaseHelper = new DatabaseHelper();
#override
void initState() {
super.initState();
refreshContacts();
checkDb();
}
Future<void> refreshContacts() async {
// Load without thumbnails initially.
var contacts = (await ContactsService.getContacts(
withThumbnails: false, iOSLocalizedLabels: true))
.toList();
setState(() {
_contacts = contacts;
});
}
void addDb(Contact c){
selectedContacts.add(c);
DatabaseHelper.addContact(c);
}
void deleteDb(Contact c){
selectedContacts.remove(c);
DatabaseHelper.deleteContact(c);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: _contacts != null
? ListView.builder(
itemCount: _contacts?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
Contact c = _contacts?.elementAt(index);
return CheckboxListTile(
value: selectedContacts.contains(c),
onChanged: (bool value) {
setState(() {
value
? addDb(c)//selectedContacts.add(c)
: deleteDb(c);//selectedContacts.remove(c);
});
},
title: Text(c.displayName ?? ""),
secondary: CircleAvatar(child: Text(c.initials())),
);
},
)
: Center(
child: CircularProgressIndicator(),
),
);
}
}
I couldn't make this code work.
is it okay to create my db with all text? from what i have understand contact_service getting its fromMap like its text so i also went with it.
Also contact_service codes looks like this:
String identifier, displayName, givenName, middleName, prefix, suffix, familyName, company, jobTitle;
String androidAccountTypeRaw, androidAccountName;
AndroidAccountType androidAccountType;
Iterable<Item> emails = [];
Iterable<Item> phones = [];
Iterable<PostalAddress> postalAddresses = [];
Uint8List avatar;
DateTime birthday;
String initials() {
return ((this.givenName?.isNotEmpty == true ? this.givenName[0] : "") +
(this.familyName?.isNotEmpty == true ? this.familyName[0] : ""))
.toUpperCase();
}
Contact.fromMap(Map m) {
identifier = m["identifier"];
displayName = m["displayName"];
givenName = m["givenName"];
middleName = m["middleName"];
familyName = m["familyName"];
prefix = m["prefix"];
suffix = m["suffix"];
company = m["company"];
jobTitle = m["jobTitle"];
androidAccountTypeRaw = m["androidAccountType"];
androidAccountType = accountTypeFromString(androidAccountTypeRaw);
androidAccountName = m["androidAccountName"];
emails = (m["emails"] as Iterable)?.map((m) => Item.fromMap(m));
phones = (m["phones"] as Iterable)?.map((m) => Item.fromMap(m));
postalAddresses = (m["postalAddresses"] as Iterable)
?.map((m) => PostalAddress.fromMap(m));
avatar = m["avatar"];
try {
birthday = DateTime.parse(m["birthday"]);
} catch (e) {
birthday = null;
}
}
static Map _toMap(Contact contact) {
var emails = [];
for (Item email in contact.emails ?? []) {
emails.add(Item._toMap(email));
}
var phones = [];
for (Item phone in contact.phones ?? []) {
phones.add(Item._toMap(phone));
}
var postalAddresses = [];
for (PostalAddress address in contact.postalAddresses ?? []) {
postalAddresses.add(PostalAddress._toMap(address));
}
final birthday = contact.birthday == null
? null
: "${contact.birthday.year.toString()}-${contact.birthday.month.toString().padLeft(2, '0')}-${contact.birthday.day.toString().padLeft(2, '0')}";
return {
"identifier": contact.identifier,
"displayName": contact.displayName,
"givenName": contact.givenName,
"middleName": contact.middleName,
"familyName": contact.familyName,
"prefix": contact.prefix,
"suffix": contact.suffix,
"company": contact.company,
"jobTitle": contact.jobTitle,
"androidAccountType": contact.androidAccountTypeRaw,
"androidAccountName": contact.androidAccountName,
"emails": emails,
"phones": phones,
"postalAddresses": postalAddresses,
"avatar": contact.avatar,
"birthday": birthday
};
}
Map toMap() {
return Contact._toMap(this);
}
In this section:
void checkDb() async{
List<Contact> selectedContacts = await DatabaseHelper.dbEmpty() ? DatabaseHelper.importContacts() : List<Contact>();
}
you cannot get contacts from database to local variable, you are created new and don't use it (because checkDb() is a method and the variable available on in methods scope). In your case, you can use FutureBuilder widget (read more here), it must me something like this:
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Contact>>(
future: DatabaseHelper.importContacts(), // You can do not check contacts availability in database, just return empty list by default
builder (context, snapshot) {
// Contacts list doesn't loaded, you need to show progress loader to a user
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(); // Here your code for list
}
);
}