beholder_flutter

Creator: coderz1093

Last updated:

0 purchases

beholder_flutter Image
beholder_flutter Images

Languages

Categories

Add to Cart

Description:

beholder flutter

Simple state management for Flutter.
This package is built to work with:

beholder_form - elegant form validation
beholder_provider - package:provider integration

Getting Started #


Define a ViewModel
class CounterViewModel extends ViewModel {}
copied to clipboard


Define state and a method to update it:
class CounterViewModel extends ViewModel {
late final counter = state(0);
void increment() => counter.value++;
}
copied to clipboard


Watch value with Observer - it will rebuild the widget when the value changes:
final vm = CounterViewModel();

// ...

Widget build(BuildContext context) {
return Observer(
builder: (context, watch) => OutlinedButton(
onPressed: vm.increment,
child: Text("${watch(vm.counter)}")
),
);
}
copied to clipboard


ViewModel #
ViewModel is used to group Observables.
Usually, you want to define ViewModel per piece of UI - it should represent UI state and related business rules.
If we need to develop a screen for searching users, its ViewModel might look like that:
class SearchUsersScreenVm extends ViewModel {
late final search = state("");
late final users = state(Loading<List<User>>()); // *

SearchUsersScreenVm() {
search.listen((_, current) => refresh());
}

Future<void> refresh() async {
users.value = Loading();
try {
final List<User> result = Api.fetchUsers(search: search.value);
users.value = Data(result);
} catch (error) {
users.value = Failure(error);
}
}
}
copied to clipboard
*Data, Failure and Loading - are helper classes. Read more about them here
Dispose #
Every class extending ViewModel has dispose method.
Call it once you don't need ViewModel to release resources:
class MyWidget extends StatefulWidget {
const MyWidget({super.key});

@override
State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
final vm = SearchUsersScreenVm();

@override
Widget build(BuildContext context) {
// ...
}

@override
void dispose() {
vm.dispose();
super.dispose();
}
}
copied to clipboard
Observables #
state #
state is a core concept in beholder.
It tracks changes to its value and notifies every observer depending on it.
Updating value #
late final counter = state(0);
void increment() {
counter.value = counter.value + 1;
// or
counter.update((current) => current + 1);
}
copied to clipboard
Listening to value changes #
counter.listen((previous, current) {
// Do something with `current`
});
copied to clipboard
computed #
Use computed to derive from state:
class User {
final String name;
User(this.name);
}

class UserProfileVm extends ViewModel {
late final user = state<User?>(null);
late final username = computed((watch) => watch(user)?.name ?? 'Guest');
}
copied to clipboard
computedFactory #
Need a parametrized computed? Use computedFactory:
class UserListVm extends ViewModel {
late final users = state(<User>[]);
late final usernameByIndex = computedFactory((watch, int index) {
return watch(users)[index];
});
}
copied to clipboard
Usage:
final vm = UserListVm();

Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) => Observer(
builder: (context, watch) {
final username = watch(vm.usernameByIndex(index));
return Text(username);
}
)
);
}
copied to clipboard
Observable as stream #
Every Observable could be converted to a stream.
class SearchScreenVm extends ViewModel {
SearchScreenVm(this.githubApi) {
final subscription = search.asStream().listen((value) {
print("Search query changed to $value");
});

disposers.add(subscription.cancel);
}

late final search = state('');
}
copied to clipboard
Utils #
AsyncValue #
AsyncValue is a default type for handling async data in asyncStates.
It has three subtypes:

Data - the future is completed successfully
Loading - the future is not completed yet
Failure - the future is completed with an error

It's a sealed class, so you can use switch to handle all cases.
Loading also has previousResult field, which is the last Data/Failure value.
It might be useful for showing old data while loading new one:
Widget build(BuildContext context) {
return Observer(
builder: (context, watch) {
final posts = watch(vm.posts);
if (posts case Loading(previousResult: Data(value: var posts))) {
return Stack(
children: [
ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) => Text(posts[index].title),
),
const CircularProgressIndicator(),
]
);
}

// ...
}
);
}
copied to clipboard
Why late? #
late allows to call instance method in field initializer.
The following:
class CounterViewModel extends ViewModel {
late final counter = state(0);
}
copied to clipboard
is a shorter (but not the same!*) version for:
class CounterViewModel extends ViewModel {
final ObservableState<int> counter;
CounterViewModel(): counter = ObservableState(0) {
disposers.add(counter.dispose);
}
}

copied to clipboard
*late fields are initialized lazily - when they are first accessed.

License

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Files In This Product:

Customer Reviews

There are no reviews.