for my app I've been trying to make a bookmarking feature that will save listings and display them on the bookmark page. I'm currently having two issues with this, firstly that whenever I try to save data, for example, ListingID, I'm able to grab it and store it perfectly fine. But when I try to save another listing, it replaces the old ListingID with the new one. I've tried turning the saved variable into a string and doing something like
await _preferences?.setString(_keybookmarks, savedID + grabbedID);
But the + variable doesn't change anything, not sure if it's because of how I've structured my code or it's not how SharedPreferences work.
And then for displaying the whole listing on the bookmark page, I want to be able to grab all the important data such as ListingID, ListingName, ListingDescription, etc, and save it, perhaps in a map and be able to have multiple of these listings in one table, and hopefully be able to delete/unbookmark the listings based on their index or ListingID.
Thanks for any advice or help!
Here's some of my app's code.
user_simple_preferences.dart
import 'package:shared_preferences/shared_preferences.dart';
class UserSimplePreferences {
static SharedPreferences? _preferences;
static String _keyBookmarks = '';
static Future init() async =>
_preferences = await SharedPreferences.getInstance();
static Future setBookmarks(String grabbedID) async =>
await _preferences?.setString(_keyBookmarks, grabbedID);
static String? getBookmarks() => _preferences?.getString(_keyBookmarks);
}
listingDetail.dart
//Triggers on button press
bool isBookmarked = false;
void toggleBookmark() {
setState(() async {
if (isBookmarked) {
isBookmarked = false;
print('IsFalse');
} else {
isBookmarked = true;
print('isTrue');
String grabbedID = widget.ListingID.toString();
await UserSimplePreferences.setBookmarks(grabbedID);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
bookmarkPage(bookmarkedID: widget.ListingID),
));
}
});
}
//My button for bookmarks
FavoriteButton(
iconDisabledColor: Colors.grey,
iconSize: 50,
isFavorite: isBookmarked,
valueChanged: (_) {
toggleBookmark();
},
),
bookmarks.dart
class bookmarkPage extends StatefulWidget {
final int bookmarkedID;
const bookmarkPage({
Key? key,
required this.bookmarkedID,
}) : super(key: key);
//rest of code...
//This grabs the saved bookmark and works on app restart.
Text(UserSimplePreferences.getBookmarks() ?? ''),
Related
I am trying to use Hive database in my flutter app. I have followed the youtube tutorials but the code is not working in my app.
I am getting the following error.
Unhandled Exception: HiveError: You need to initialize Hive or provide a path to store the box.
Here is my code...
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final dbDir = await getApplicationDocumentsDirectory();
await Hive.initFlutter(dbDir.path);
Hive.registerAdapter(AddToCartAdapter());
await Hive.openBox<AddToCart>('addToCart');
runApp(const MyApp());
}
Future cartlist(String name, String unit, String qty, String price) async {
await Hive.openBox("addToCart");
final cart = AddToCart()
..name = name
..unit = unit
..qty = qty
..price = price;
final Box = Boxes.getAddToCart();
Box.add(cart)
;
}
I also use Hive as a local database. I noticed that I din't specify subDir param and just initiated like that:
await Hive.initFlutter();
Hive.registerAdapter(AddToCartAdapter());
The problem is occurring to you, because you are using Hive.initFlutter with dbDir.path path and while using Hive.openBox you are not specifying that path again, You need to specify same path for openBox method.
I suggest your code should be like that:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
Hive.registerAdapter(AddToCartAdapter());
HiveBoxes.cartBox = await Hive.openBox<AddToCart>(Constants.cartBoxName);
runApp(const MyApp());
}
To make your code better, I suggest some stuffs like creating a class for constant names to prevent typos and creating some static variable to get access Hive Boxes easily like that:
class Constants{
static const cartBoxName = 'CartBox';
}
class HiveBoxes{
static late Box cartBox;
}
About your method you can do like that:
Future cartlist(String name, String unit, String qty, String price) async {
///we don't need to open box again, so please delete this commented code
//await Hive.openBox("addToCart");
final cart = AddToCart()
..name = name
..unit = unit
..qty = qty
..price = price;
HiveBoxes.cartBox.put("sampleKey", cart);;
// It's also wrong code: Box.add(cart);
}
I am developing an application in Flutter where I need to implement an image selection function like in instagram.
But there is an issue, my app UI is freezing when trying to get and compress files from user phone gallery.
This is my first experience with flutter isolates, but as far as i know it should work without freezes.
Here is an image for a better understanding of what i want to do.
This is a function that calls getFiles function in isolation.
Here i get paths of user phone gallery files and pass them to another function in order to compress and get files for rendering.
Future fetchImages({ bool fetchMore = false, bool force = false }) async {
if (!fetchMore) {
setState(() => fetched = false);
}
if (force) {
assetsCount = await assetPathEntity!.assetCountAsync;
page = 0;
files.clear();
}
if (assetsCount == 0 || page >= (assetsCount / pageSize)) {
return setState(() => fetched = true);
}
final assetEntities = await assetPathEntity!.getAssetListPaged(page: page++, size: pageSize);
lastCompletedIndex = files.length;
final receivePort = ReceivePort();
final completer = Completer();
getFiles(receivePort.sendPort, assetEntities);
try {
receivePort.listen((filesPaths) {
for (final filePath in filesPaths) {
files.add({
"path": filePath,
"compressedFile": null,
});
}
if (scaledFile == null && files.isNotEmpty) {
scaledFile = File(files.first["path"]);
}
compressAlbumImages();
completer.complete();
setState(() => fetched = true);
}).onError((_) {
compressAlbumImages();
completer.complete();
setState(() => fetched = true);
});
await Future.wait([completer.future]);
} catch (_) { }
finally {
receivePort.close();
}
}
This is getFiles function that runs in isolation
void getFiles(SendPort sendPort, List<AssetEntity> assetEntities) async {
final List<String> filesPaths = [];
for (final assetEntity in assetEntities) {
try {
final file = await assetEntity.file;
if (file != null) {
filesPaths.add(file.path);
}
} catch (_) { }
}
sendPort.send(filesPaths);
}
This is a function that calls compressImages function and adds any value to refresh the list of images
Here i pass the paths and get compressed files for rendering.
void compressAlbumImages() async {
final receivePort = ReceivePort();
final completer = Completer();
compressImages(receivePort.sendPort, files, lastCompletedIndex);
try {
receivePort.listen((compressedFilesWithPath) {
files = compressedFilesWithPath;
fileStreamCt.sink.add(1);
completer.complete();
}).onError((_) {
completer.complete();
});
await Future.wait([completer.future]);
} catch (_) {}
finally {
receivePort.close();
}
return;
}
This is an image compression function that runs in isolation
void compressImages(SendPort sendPort, List<Map<String, dynamic>> files, int startFromIndex) async {
final List<String> filesToBeRemoved = [];
for (int idx = startFromIndex; idx < files.length; idx++) {
final file = files[idx];
try {
final compressedFile = await FlutterNativeImage.compressImage(
file["path"],
quality: 20,
percentage: 20,
targetHeight: 300,
targetWidth: 300,
);
file["compressedFile"] = compressedFile;
} catch (_) {
filesToBeRemoved.add(file["path"]);
}
}
if (filesToBeRemoved.isEmpty) {
return sendPort.send(files);
}
final compressedFilesWithPaths = files
.whereNot((element) => filesToBeRemoved.contains(element["path"]))
.toList();
sendPort.send(compressedFilesWithPaths);
}
And finally i render compressed images
return StreamBuilder(
stream: fileStreamCt.stream,
builder: (ctx, AsyncSnapshot<int> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("LOADING...");
}
if (snapshot.hasError) {
return Text("AN ERROR OCCURRED");
}
return buildAlbumImages();
}
);
Render like this
Image.file(compressedImage);
If in short - When i'm getting user phone gallery files && compress them, my app UI starts freezing.
I don't know why it's freezing.
I tried the same process but with file.readAsBytes() and to render like Image.memory(compressedFileBytes), but it was useless.
I would be very grateful for any help.
Thanks in advance.
In your code you never actually create an isolate. The 'isolate code' in getFile, compressImages etc simply runs on the main isolate and indeed will block the UI.
Per documentation, you create an isolate with Isolate.spawn and pass only the sendPort. The isolate then must send back a receivePort, and the main thread uses that port to send the data you want to isolate to process (like assetEntities), processes it and sends the results back to the main thread. It's a bit complicated, and requires different function signatures than you have here.
Fortunately, a much easier way to accomplish what you want (still using Isolates that won't block the UI) is to use the compute function from the dart:async package:
Change the signature of your getFiles function to Future<List<String>> getFiles(List<AssetEntity> assetEntities) async and do in it what you need to do, returning the list of filesPaths as you do now. Importantly, getFiles must be a top level or a static function, it cannot be a regular class method. Then, where you need the calculation done you use something like var filesPaths = await compute(getFiles, assetEntities). Now, the getFiles function is called in an isolate, and the return value is given back to you on the main isolate. The nice thing is that now this looks a lot like regular await call, no need for sendPorts etc. You can do the same thing for your other heavy calculation methods.
One (big) constraint with isolates is the type of argument you can pass to and from an isolate, see here. Those same constraints apply here, because under the hood the compute function also uses sendPorts etc.
I have the code below that I thought would run whenever the given screen is reached, i.e. whenever the user goes to this screen. This screen creates some temporary files for the user. I don't need them after the user leaves the screen, so I wanted to flush them everytime the user reaches this screen. However, the line with await cleanupTempAudioFiles(); doesn't seem to be doing its job.
#override
void initState() {
super.initState();
initialize();
}
void initialize() async {
uid = auth.currentUser;
filesInProgressFileDirString = systemTempDir.path + '/App/AppAudioFiles/FilesInProgress/';
fileInProgressFileDir = await Directory(fileInProgressFileDirString).create(recursive: true);
myRecorder = await FlutterSoundRecorder().openRecorder();
myPlayer = await FlutterSoundPlayer().openPlayer();
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 10),
)..addListener(() {
setState(() {});
});
controller?.reset();
await cleanupTempAudioFiles(); //This code seems to not be running every time the screen is opened
setState(() {
sendableFileExists = 0;
});
}
Future<void> cleanupTempAudioFiles() async {
final dir = Directory(filesInProgressFileDir.path);
final List<FileSystemEntity> files = await dir.list().toList();
files.forEach((file) async {
if (file.path == filesInProgressFileDir.path + currentAppFilename) {
await file.delete();
}
if (file.path == filesInProgressFileDir.path + currentAppFilename + 'High.mp3') {
await file.delete();
}
if (file.path == filesInProgressFileDir.path + currentAppFilename + 'Low.mp3') {
await file.delete();
}
});
}
The initState() function is called when your object is put into the widget tree. This is not the same as every time it is displayed. Navigator.pop() for example will show the screen without re-inserting the widget into the tree. see https://api.flutter.dev/flutter/widgets/State/initState.html
To run every time the user sees a widget you should put the code into the build function or consider #overriding dispose(), didChangeDependencies() or
didUpdateWidget() instead to get the right part of the widget lifecycle.
i have been trying to get the download URL for my images in firebase storage so I can add it to my listview builders "NetworkCatched Images" for each of my items in the list, as you can see from my code below, I first declared a variable at the beginning of my stateful class called "URL" so I could change the value by making it equal to the download URL i get from firebase storage, but it seems like the async function I'm using doesn't even run because i made sure it prints out the value of the downloaded URL after it is done, but i see nothing in my debug console, where am going wrong here?
i keep getting this error No object exists at the desired reference.
by the way, the "thesnapshot.data()['image']" in my code is equal to the name of the image file e.g books.jpg which is the exact name of the file and it is in a folder called category as you can see below, would really appreciate some enlightenment on this, thanks
class Home extends State<HomeScreen> {
var url;
ListView.builder(shrinkWrap: true, padding: EdgeInsets.all(0), physics: NeverScrollableScrollPhysics(), itemCount: snapshot.data.documents.length, itemBuilder: (BuildContext context, int index)
{
DocumentSnapshot thesnapshot = snapshot.data.docs[index];
current_category = thesnapshot.data()['category'];
printUrl() async {
Reference ref = FirebaseStorage.instance.ref().child("category/" + thesnapshot.data()['image'].toString());
var togo = (await ref.getDownloadURL()).toString();
setState(() {
url = togo;
print(url);
});
}
printUrl();
I think that the .ref() included in your reference doesn't go thereas well as the thesnapshot.data()['image'].toString().
Try something like this
Future<void> downloadURLExample() async {
String downloadURL = await firebase_storage.FirebaseStorage.instance
.ref('users/123/avatar.jpg')
.getDownloadURL();
Extracted from the documentation
I'm modifying Pesto example https://github.com/flutter/flutter/blob/76844e25da8806a3ece803f0e59420425e28a405/examples/flutter_gallery/lib/demo/pesto_demo.dart to receive new recipes from the net, by adding the following function:
void loadMore(BuildContext context, query) {
HttpClient client = new HttpClient();
client.getUrl(Uri.parse("http://10.0.2.2:5000/search/" + query ))
.then((HttpClientRequest request) {
return request.close();
})
.then((HttpClientResponse response) {
// Process the response.
response.transform(UTF8.decoder).listen((contents) {
// handle data
var decoded = JSON.decode(contents);
var rec = new Recipe(
name: decoded['title'],
author: decoded['author'],
professionIconPath: 'images/1.png',
description: decoded['description'],
imageUrl: 'http://example.jpg',
);
kPestoRecipes.add(job);
//Navigator.push(context, new MaterialPageRoute<Null>(
// settings: const RouteSettings(name: "/"),
// builder: (BuildContext context) => new PestoHome(),
//));
});
});
}
and I binded loading additional recipe to clicking interface button:
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.edit),
onPressed: () {
scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text('Adding new recipe')
));
loadMore(context, "test");
},
),
But how do I redraw home page when new recipe received? I've tried to
the part with Navigator you've seen commented out, but it didn't work. I also tried
new PestoDemo();
but it showed new recipe only for a moment and didn't feel like a proper way to do re-drawing.
You should call setState whenever you change data that affects what the build() function for a given State object returns:
https://docs.flutter.io/flutter/widgets/State/setState.html
For example:
setState(() {
_recipes.add(job);
});
If you do this, I'd recommend changing kPestoRecipes from being a global constant to being a private member variable of a State subclass. That will probably involve passing the list around from one widget to another rather than having all the widget refer to the global constant.
One other tip: when you get the HttpClientResponse, you should check the mounted property of the State object. If mounted is false, that means the widget has been removed from the tree and you might want to ignore the network response.