flutter: Update values state in separate class with stream instead of setState()? - flutter

Im currently building an app that gets data from a separate class.
When the list devices gets filled, it should add an container to a Scrolable list.
I already managed to do that part, but.. cause the state of the devices list won't update on change the ui won't add an container either. And I can't use set state in the separate class..
void initState() {
// TODO: implement initState
super.initState();
getDevices();
}
Future<void> getDevices() async {
setState(() async {
deviceLength = drs.devices.length;
await drs.allDevices(specificDevices: 'DRS');
});
}
set state only updates deviceLength on start, but won't continue.
The length of devices is defined by following stream in a separate class Bluetooth, that adds objects to the List devices :
Future<Stream<void>> _findSingleResult({searchFor: ''}) async {
_flutterBlue.scanResults.listen((results) async {
// Return found Devices
try {
print('Device Found ? $deviceFound');
print(results.last.device.name);
//Does found device equal searched Device?
if (results.last.device.name.contains(searchFor)) {
deviceFound = true;
devices.add(results.last.device);
print('Device found...');
await _flutterBlue.stopScan();
return;
}
_counter++;
} catch (e) {
print(e);
}
});
}
If anyone knows how to solve this Issue pls help :)

Related

Riverpod provider is always null

I am using riverpod for my state manegement in my flutter app.
Riverpod offers a feature for combined providers, but my dependent provider does not update and always returns null.
By clicking one of the pins (secrets) on the map, my "selectedSecretProvider" is updated (default is null). This should trigger the initialization of my audio player. And by clicking play, the sound of the current _selectedSecret should play. So my "selectedTrackProvder" is dependent on my "selectedSecretProvider":
final selectedTrackProvider = StateNotifierProvider<SelectedTrack, Track>((ref) {
Secret? selectedSecret = ref.watch(selectedSecretProvider);
return SelectedTrack(selectedSecret);
});
Here is my selectedTrack class:
class SelectedTrack extends StateNotifier<Track> {
SelectedTrack(this.selectedSecret) : super(Track.initial());
Secret? selectedSecret;
#override
void dispose() {
...
}
void initAudioPlayer() {
...
}
Future<int> play() async {
print(selectedSecret);
return ...
}
}
So why does it always print null, when clicking play?
(Btw. my bottom_panel_sheet is showing the correct data and also consumes the "selectedSecretProvider".)
I wouldn't say the way you're creating your StateNotifierProvider is wrong, but I think the following is a better approach that should solve your problem.
final selectedTrackProvider = StateNotifierProvider<SelectedTrack, Track>((ref) {
return SelectedTrack(ref);
});
class SelectedTrack extends StateNotifier<Track> {
SelectedTrack(this.ref) : super(Track.initial());
final ProviderReference ref;
Future<int> play() async {
final selectedSecret = ref.read(selectedSecretProvider);
print(selectedSecret);
return ...
}
}
This way you don't create a new StateNotifier every time the selectedSecretProvider updates, instead opting to read the current value of the selectedSecretProvider when attempting to call play.

How to update a list in Flutter with Bloc/Cubit?

my UI consists of a table of objects Apples.
Every cell in the table:
has an ADD button, if the apple is NOT present for that cell
it shows the apple and has a DELETE button, if the apple is present
The main page of my application is calling the following widget to LOAD the LIST of apples from an API. And also the ADD and DELETE functions communicate with the same API for adding and deleting the apple.
class ApplesLoader extends StatefulWidget {
#override
_ApplesLoaderState createState() => _ApplesLoaderState();
}
class _ApplesLoaderState extends State<ApplesLoader> {
#override
void initState() {
super.initState();
BlocProvider.of<ApplesCubit>(context).getAll();
}
#override
Widget build(BuildContext context) {
return BlocConsumer<ApplesCubit, ApplesState>(
listener: ...,
builder: (ctx, state) {
if (!(state is ApplesLoaded)) {
return circularProgressIndicator;
}
return ApplesViewer(state.Apples);
},
);
}
}
So after that ApplesViewerhas the list of apples and can correctly display the grid.
But now I have two problems:
When I press the ADD button or the DELETE button, all the application is rebuilt, while I could actually just re-paint the cell. But I don't know how to avoid this, because I also need to communicate to the application that the list of apples is actually changed. I don't want to rebuild the entire UI because it seems inefficient since what is actually changing on my page is only the selected cell.
When I press the ADD button or the DELETE button, I would like to show a circular progress indicator that replaces the button (while waiting for the API to actually creating/deleting the apple). But when the list changes, all the application is rebuilt instead. So, I am not sure how to achieve this desired behavior.
Could you help me in solving those two issues?
Or advise me for a change of infrastructure in case I am making some mistake in dealing with this?
If needed, this is the code for ApplesCubit
class ApplesCubit extends Cubit<ApplesState> {
final ApplesRepository _repository;
ApplesCubit(this._repository) : super(ApplesLoading());
Future<void> getAll() async {
try {
final apples = await _repository.getAll();
emit(ApplesLoaded(List<Apple>.from(apples)));
} on DataError catch (e) {
emit(ApplesError(e));
}
}
Future<void> create(List<Apple> apples, Apple appleToAdd) async {
try {
final newApple = await _repository.create(appleToAdd);
final updatedApples = List<Apple>.from(apples)..add(newApple);
emit(ApplesLoaded(updatedApples));
} on DataError catch (e) {
emit(ApplesError(e));
}
}
Future<void> delete(List<Apple> Appless, Apple appleToDelete) async {
try {
await _repository.deleteApples(appleToDelete);
final updatedApples = List<Apple>.from(Appless)..remove(appleToDelete);
emit(ApplesLoaded(updatedApples));
} on DataError catch (e) {
emit(ApplesError(e));
}
}
}

where to load model from file in flutter apps?

Suppose I store my data in a dedicated repo class like so:
class UrlEntry {
final String url;
final String title;
UrlEntry({#required this.url, this.title});
}
class UrlRepository with ChangeNotifier {
List<UrlEntry> urlEntries = new List<UrlEntry>();
// Returns the urls as a separate list. Modifyable, but doesnt change state.
List<UrlEntry> getUrls() => new List<UrlEntry>.from(urlEntries);
add(UrlEntry url) {
this.urlEntries.add(url);
print(
"url entry ${url.url} added. Now having ${urlEntries.length} entries ");
notifyListeners();
}
removeByUrl(String url) {
var beforeCount = this.urlEntries.length;
this.urlEntries.removeWhere((entry) => entry.url == url);
var afterCount = this.urlEntries.length;
if (beforeCount != afterCount) notifyListeners();
print("removed: ${beforeCount != afterCount}");
}
save() async {
final storageFile = await composeStorageFile();
print("storage file is '${storageFile.path}");
if (await storageFile.exists()) {
print("deleting existing file");
await storageFile.delete();
}
if (urlEntries == null || urlEntries.length < 1) {
print("no entries to save");
return false;
}
print(
"saving ${urlEntries.length} url entries to file $storageFile} ...");
for (var entry in urlEntries) {
await storageFile.writeAsString('${entry.url} ${entry.title}',
mode: FileMode.append);
}
}
Future<File> composeStorageFile() async {
Directory storageDir = await getApplicationDocumentsDirectory();
return File('${storageDir.path}/url_collection.lst');
}
void dispose() async {
super.dispose();
print("disposing ...");
urlEntries.clear();
this.urlEntries = null;
}
load() async {
final storageFile = await composeStorageFile();
if (!await storageFile.exists()) {
print("storage file ${storageFile.path} not existing - not loading");
return false;
}
print("loading file ${storageFile.path}");
urlEntries = List <UrlEntry> () ;
final fileLines = storageFile.readAsLinesSync() ;
for (var line in fileLines) {
var separatorIndex = line.indexOf(' ') ;
final url = line.substring(0, separatorIndex) ;
var title = line.substring(separatorIndex+1) ;
if (title == 'null') title = null ;
urlEntries.add(new UrlEntry(url: url, title: title)) ;
}
notifyListeners() ;
}
}
Above code has several issues I unfortunately donnot know how to circumvent:
most of the methods of UrlRepository are async. This is because of getApplicationDocumentsDirectory() being async. I think former is an absolute flaw but introducing semaphores here to create an artificial bottleneck would pollute the code, so I still stick to async; but call me old-fashioned - I dont like the idea having save and load operations being theoretically able to overlap each other. I mean, with getApplicationDocumentsDirectory, we're talking about a simple configurational detail that will not need much computational power to compute, nor to store, nor will it change that often and it pollutes the code with otherwise unnessecary stuff. So, Is there another way to get the results of getApplicationDocumentsDirectory() without await / async / then ?
If this is not the case - where should I put the call to save()? My first idea was to save data not every model change, but instead at the latest possible executional place, which is one of the dispose-related methods, like so:
class MyAppState extends State<MyApp> {
UrlRepository urlRepository;
...
#override
void deactivate() async {
await urlRepository.save() ;
super.deactivate();
}
Unfortunately this results in urlRepository.save() being executed only the half, no matter whether I call it in a unit test, on a avd or on a real device. Right in the middle its terminated - I checked that with printouts. I think this is because, being forced again to make a completely unrelated method async (here deactivate()), I have to accept that execution is not granted to terminate at the return command, but earlier (?). I tried to put the call to MyState.dispose() as well as to urlRepository.dispose() with the same result except I cannot make the dispose methods async and hence just call save() async and hope everything has been saved before super.dispose() kicks in,...
I thought it natural to load the repositotry state inside of initState(), but I want to make sure that either the load has completed before creating widgets (ie calling the builder), or will be loaded after all widgets have already been in place so the model change will trigger rebuild. Since load() has to be async for known reasons and initState is not, I cannot assure even one of above cases and stick with urlRepository.load() and hope the best. So, where to put the call to urlRepository.load() ?
First: You have to use async/await methods because you don't know what the user's device may be doing while running your application, so even though the device might be performing a process that "doesn't need much power computational" that process may take a little longer than expected.
Second: Do not trust in deactivate() and dispose() functions, the user could kill your app and it would never do the save process. I'm not really sure how to automate this process. I suggest you do it manually somewhere in your code.
Third: Don't use initState() to load your data, you should use a FutureBuilder in which the future parameter is your urlRepository.load() function, while its loading you can show a CircularProgressIndicator() and if it has data you show your widget.
Example:
#override
Widget build() {
return FutureBuilder(
future: urlRepository.load() // without await keyword
builder: (context, snapshot) {
if(!snapshot.hasData)
return CircularProgressIndicator();
return YourWidget(); // if snapshot.hasData is true the process has finished
}
);
}
Psdt: It might be useful if urlRepository.load() would return something like a bool. Doing this you could show a widget if snapshot.data is true or another widget if snapshot.data is false.

Flutter Firebase stops listening after push and pop operation

I'm saving Firebase data in a list using on child Added listener and on child changed listener, it works good, but when i perform push operation and use pop to back to the screen the listeners stops listening.
Is there any solutions to fix this.
I used this code from Stackoverflow : Code
Sorry for this silly post, i'm new to flutter, I fixed this by using Future method in listeners.
#GrahamD you can find my code here.
My code:
var childAddedListener;
var childChangedListener;
#override
void initState(){
FirebaseDatabase database;
database = FirebaseDatabase.instance;
database.setPersistenceEnabled(true);
database.setPersistenceCacheSizeBytes(10000000);
reference = database.reference().child('Sweets');
childAddedListener = reference.onChildAdded.listen(_onEntryAddedShop);
childChangedListener = reference.onChildChanged.listen(_onEntryChangedShop);
super.initState();
}
On Child Added Listener
Future _onEntryAddedShop(Event event) {
setState(() {
itemsShop.add(Sweets.fromSnapshot(event.snapshot));
});
}
On Child Changed Listener
Future _onEntryChangedShop(Event event) {
print('Child Changed');
var old = itemsShop.singleWhere((entry) {
return entry.id == event.snapshot.key;
});
setState(() {
itemsShop[itemsShop.indexOf(old)] = Sweets.fromSnapshot(event.snapshot);
});
getItemIds();
}
Cancel them in dispose
#override
dispose() {
childAddedListener.cancel();
childChangedListener.cancel();
super.dispose();
}

Skipped x frames! The application may be doing too much work on its main thread. What does this error signify and how to solve this?

I'm trying to do authentication with firbase in my Flutter app. Once the user is signed in and goes to the Authenticted screen, this error shows up.
I'm using google_signin plugin with simple logics.
bool isAuth = false;
//check if the user is signed in
#override
void initState() {
super.initState();
googleSignIn.onCurrentUserChanged.listen((account) {
handleSignIn(account);
}, onError: (err) {
print("Error signing in: $err");
});
//maintain the signin
googleSignIn.signInSilently(suppressErrors: false).then((account) {
handleSignIn(account);
}).catchError((err) {
print("Error signing in: $err");
});
}
handleSignIn(GoogleSignInAccount account) {
if (account != null) {
print('User signed in!: $account');
setState(() {
isAuth = true;
});
} else {
setState(() {
isAuth = false;
});
}
}
//sign in using google
login() {
googleSignIn.signIn();
}
logout() {
googleSignIn.signOut();
}
Widget buildAuthScreen() {
return Center(
child: RaisedButton(
child: Text("LogOut"),
onPressed: logout(),
),
);
}
then the unauth screen has basic login layout for signining in...
If you are not doing anything unnecessary in your main, you can just ignore it.
the number of skipped frames is dependent on:
The number of static and top-level variables that need initiation on startup.
Speed of the CPU that will be doing computational work, like parsing JSONs at startup.
Speed of the device storage for initiating databases and shared preferences.
Whether you're using a debug or release build of your app.
The speed of the network connection in case of needing some necessary data before the app starts up.
Whether you're using an emulator or a physical device.
The size of the widget tree in your home page.
This is something you can't control sometimes, I faced this situation where an old android phone showed it skipped 180 frames where as a new phone said it skipped 25.