How to add an object to flutter_realm? - flutter

The following Exception is thrown when I try to add an object
Maybe I misunderstand how functions work.
I couldn't find something helpful in documentation too
Unhandled Exception: Unhandled error PlatformException(-1, Attempting to create an object of type 'Country' with an existing primary key value ''., {
RLMRealmCoreVersion = "";
RLMRealmVersion = "3.17.3";
}, null) occurred in Instance of 'CountriesBloc'.
Here is my code . I have an CountriesToRealm event and I want to save my objects in flutter_realm when I add the event . I add it when I load countries by http request
if (event is CountriesToRealm) {
for (int i = 0; i < event.countries.length; ++i) {
Country country = event.countries[i];
print(country.name);
if (country.name == null || country.name.isEmpty) {}
final query = Query('Country').equalTo('name', country.name);
final List all = await realm.objects(query);
if (all.isEmpty) {
await realm.createObject('Country', country.toMap(withId: true));
}
}
final List allcountries = await event.realm.allObjects('Country');
if (allcountries.length == event.countries.length) {
yield CountriesAddedToRealmState();
} else {
yield FailedToAddCountriesState();
}
}
Also Realm Provider
import 'package:flutter/material.dart';
import 'package:flutter_realm/flutter_realm.dart';
import 'package:uuid/uuid.dart';
class RealmProvider extends StatefulWidget {
final Widget Function(Realm) builder;
const RealmProvider({this.builder}) : super();
#override
_RealmProviderState createState() => _RealmProviderState();
}
class _RealmProviderState extends State<RealmProvider> {
Future<Realm> realm;
#override
void initState() {
super.initState();
final configuration = Configuration(inMemoryIdentifier: Uuid().v4());
realm = Realm.open(configuration);
}
#override
Widget build(BuildContext context) {
return FutureBuilder<Realm>(
future: realm,
builder: (context, snapshot) {
if (snapshot.data == null) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: CircularProgressIndicator(),
),
);
}
return widget.builder(snapshot.data);
},
);
}
}
Thank you for your help

Related

ChangeNotifierProvider does not update the model

i am quite new with flutter. I am trying to add a ChangeNotifierProvider into my app. I use flutter_azure_b2c to log in a user, in order to handle to login outcome I have the following code:
AzureB2C.registerCallback(B2COperationSource.POLICY_TRIGGER_INTERACTIVE,
(result) async {
if (result.reason == B2COperationState.SUCCESS) {
List<String>? subjects = await AzureB2C.getSubjects();
if (subjects != null && subjects.isNotEmpty) {
B2CAccessToken? token = await AzureB2C.getAccessToken(subjects[0]);
if (!mounted || token == null) return;
final encodedPayload = token.token.split('.')[1];
final payloadData =
utf8.fuse(base64).decode(base64.normalize(encodedPayload));
final claims = Claims.fromJson(jsonDecode(payloadData));
var m = Provider.of<LoginModel>(context);
m.logIn(claims);
}
}
});
The problem is that when it arrives to var m = Provider.of<LoginModel>(context); the execution stops with out errors without executing m.logIn(claims);, so the model is not changed and the consumer is not called.
Any idea?
This is my consumer:
class App extends StatelessWidget {
const App({super.key});
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => LoginModel(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: appTheme,
home: Consumer<LoginModel>(
builder: (context, value, child) =>
value.claims != null ? const Home() : const Login(),
)),
);
}
}
class LoginModel extends ChangeNotifier {
Claims? _claims;
logIn(Claims claims) {
_claims = claims;
notifyListeners();
}
logOut() {
_claims = null;
notifyListeners();
}
Claims? get claims => _claims;
}
My LoginWidget:
class Login extends StatefulWidget {
const Login({super.key});
#override
LoginState createState() => LoginState();
}
class LoginState extends State<Login> {
B2CConfiguration? _configuration;
checkLogin(BuildContext context) async {
List<String>? subjects = await AzureB2C.getSubjects();
if (subjects != null && subjects.isNotEmpty) {
B2CAccessToken? token = await AzureB2C.getAccessToken(subjects[0]);
if (!mounted || token == null) return;
final encodedData = token.token.split('.')[1];
final data =
utf8.fuse(base64).decode(base64.normalize(encodedData));
final claims = Claims.fromJson(jsonDecode(data));
var m = Provider.of<LoginModel>(context, listen: true);
m.logIn(claims); //<-- debugger never reaches this line
}
}
#override
Widget build(BuildContext context) {
// It is possible to register callbacks in order to handle return values
// from asynchronous calls to the plugin
AzureB2C.registerCallback(B2COperationSource.INIT, (result) async {
if (result.reason == B2COperationState.SUCCESS) {
_configuration = await AzureB2C.getConfiguration();
if (!mounted) return;
await checkLogin(context);
}
});
AzureB2C.registerCallback(B2COperationSource.POLICY_TRIGGER_INTERACTIVE,
(result) async {
if (result.reason == B2COperationState.SUCCESS) {
if (!mounted) return;
await checkLogin(context);
}
});
// Important: Remeber to handle redirect states (if you want to support
// the web platform with redirect method) and init the AzureB2C plugin
// before the material app starts.
AzureB2C.handleRedirectFuture().then((_) => AzureB2C.init("auth_config"));
const String assetName = 'assets/images/logo.svg';
final Widget logo = SvgPicture.asset(
assetName,
);
return SafeArea(
child: //omitted,
);
}
}
I opened an issue as well, but it did not help me.
Try this
var m = Provider.of<LoginModel>(context, listen: false)._claims;
You are using the Provider syntax but not doing anything really with it. You need to set it like this Provider.of<LoginModel>(context, listen: false).login(claims) and call it like this Provider.of<LoginModel>(context, listen: false)._claims;
I fixed it, moving the callback registrations from the build method to the initState method.

Flutter Custom State Management

What I am trying to achieve is a small custom state management solution that I believe is powerful enough to run small and large apps. The core is based on the ValueNotifier and ValueListenable concepts in flutter. The data can be accessed anywhere in the app with out context since I am storing the data like this:
class UserData {
static ValueNotifier<DataLoader<User>> userData =
ValueNotifier(DataLoader<User>());
static Future<User> loadUserData() async {
await Future.delayed(const Duration(seconds: 3));
User user = User();
user.age = 23;
user.family = 'Naoushy';
user.name = 'Anass';
return user;
}
}
So by using UserData.userData you can use the data of the user whenever you want. Everything works fine until I encountered a problem of providing a child to my custom data consumer that rebuilds the widget when there is a new event fired. The DataLoader class looks like this:
enum Status { none, hasError, loading, loaded }
class DataLoader<T> {
Status status = Status.none;
T? data;
Object? error;
bool get hasError => error != null;
bool get hasData => data != null;
}
which is very simple. Now the class for consuming the data and rebuilding looks like this:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:testing/utils/dataLoader/data_loader.dart';
class DataLoaderUI<T> extends StatefulWidget {
final ValueNotifier<DataLoader<T>> valueNotifier;
final Widget noneStatusUI;
final Widget hasErrorUI;
final Widget loadingUI;
final Widget child;
final Future<T> future;
const DataLoaderUI(
{Key? key,
required this.valueNotifier,
this.noneStatusUI = const Text('Data initialization has not started'),
this.hasErrorUI = const Center(child: Text('Unable to fetch data')),
this.loadingUI = const Center(
child: CircularProgressIndicator(),
),
required this.child,
required this.future})
: super(key: key);
#override
State<DataLoaderUI> createState() => _DataLoaderUIState();
}
class _DataLoaderUIState extends State<DataLoaderUI> {
Future startLoading() async {
widget.valueNotifier.value.status = Status.loading;
widget.valueNotifier.notifyListeners();
try {
var data = await widget.future;
widget.valueNotifier.value.data = data;
widget.valueNotifier.value.status = Status.loaded;
widget.valueNotifier.notifyListeners();
} catch (e) {
log('future error', error: e.toString());
widget.valueNotifier.value.error = e;
widget.valueNotifier.value.status = Status.hasError;
widget.valueNotifier.notifyListeners();
}
}
#override
void initState() {
super.initState();
log('init state launched');
if (!widget.valueNotifier.value.hasData) {
log('reloading or first loading');
startLoading();
}
}
//AsyncSnapshot asyncSnapshot;
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<DataLoader>(
valueListenable: widget.valueNotifier,
builder: (context, dataLoader, ui) {
if (dataLoader.status == Status.none) {
return widget.noneStatusUI;
} else if (dataLoader.status == Status.hasError) {
return widget.hasErrorUI;
} else if (dataLoader.status == Status.loading) {
return widget.loadingUI;
} else {
return widget.child;
}
});
}
}
which is also simple yet very effective. since even if the initState function is relaunched if the data is already fetched the Future will not relaunch.
I am using the class like this:
class TabOne extends StatefulWidget {
static Tab tab = const Tab(
icon: Icon(Icons.upload),
);
const TabOne({Key? key}) : super(key: key);
#override
State<TabOne> createState() => _TabOneState();
}
class _TabOneState extends State<TabOne> {
#override
Widget build(BuildContext context) {
return DataLoaderUI<User>(
valueNotifier: UserData.userData,
future: UserData.loadUserData(),
child: Text(UserData.userData.value.data!.name??'No name'));
}
}
The error is in this line:
Text(UserData.userData.value.data!.name??'No name'));
Null check operator used on a null value
Since I am passing the Text widget as an argument with the data inside it. Flutter is trying to pass it but not able to since there is no data yet so its accessing null values. I tried with a normal string and it works perfectly. I looked at the FutureBuilder widget and they use a kind of builder and also the ValueLisnableBuilder has a builder as an arguement. The problem is that I am not capable of creating something like it for my custom solution. How can I just pass the child that I want without having such an error and without moving the ValueLisnable widget into my direct UI widget?
I have found the solution.
Modify the DataLoaderUI class to this:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:testing/utils/dataLoader/data_loader.dart';
class DataLoaderUI<T> extends StatefulWidget {
final ValueNotifier<DataLoader<T>> valueNotifier;
final Widget noneStatusUI;
final Widget hasErrorUI;
final Widget loadingUI;
final Widget Function(T? snapshotData) child;
final Future<T> future;
const DataLoaderUI(
{Key? key,
required this.valueNotifier,
this.noneStatusUI = const Text('Data initialization has not started'),
this.hasErrorUI = const Center(child: Text('Unable to fetch data')),
this.loadingUI = const Center(
child: CircularProgressIndicator(),
),
required this.child,
required this.future})
: super(key: key);
#override
State<DataLoaderUI<T>> createState() => _DataLoaderUIState<T>();
}
class _DataLoaderUIState<T> extends State<DataLoaderUI<T>> {
Future startLoading() async {
widget.valueNotifier.value.status = Status.loading;
widget.valueNotifier.notifyListeners();
try {
var data = await widget.future;
widget.valueNotifier.value.data = data;
widget.valueNotifier.value.status = Status.loaded;
widget.valueNotifier.notifyListeners();
} catch (e) {
log('future error', error: e.toString());
widget.valueNotifier.value.error = e;
widget.valueNotifier.value.status = Status.hasError;
widget.valueNotifier.notifyListeners();
}
}
#override
void initState() {
super.initState();
log('init state launched');
if (!widget.valueNotifier.value.hasData) {
log('reloading or first loading');
startLoading();
}
}
//AsyncSnapshot asyncSnapshot;
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<DataLoader<T>>(
valueListenable: widget.valueNotifier,
builder: (context, dataLoader, ui) {
if (dataLoader.status == Status.none) {
return widget.noneStatusUI;
} else if (dataLoader.status == Status.hasError) {
return widget.hasErrorUI;
} else if (dataLoader.status == Status.loading) {
return widget.loadingUI;
} else {
return widget.child(dataLoader.data);
}
});
}
}
and use it like this:
DataLoaderUI<User>(
valueNotifier: UserData.userData,
future: UserData.loadUserData(),
child: (user) {
return Text(user!.name ?? 'kk');
});
Take a look at my version of the same sort of state management approach here: https://github.com/lukehutch/flutter_reactive_widget

Random parameter for FutureBuilder

Here I have a StatefulWidget in which I want to get a random pet each time from a random url. Also, I have a condition for the random pet, if the condition is true, the pet will be shown, otherwise the random url and random pet should be selected again. I attached my code below, and the problem is the url only changes when the condition is false, but I want it to be randomly selected each time.
Putting the API.get_pets(init_random_url); in the future parameter of the FutureBuilder will solve the random selection but if the condition is false the URL and the pet would change two or three times, after searching about it and reading FutureBuilder documentation I put it in the initState and requestAgain and build, but I recognized the selectedURL in the build function does not work and the widget is stucked in the same URL until the condition gets false value.
import 'dart:developer';
import 'package:double_back_to_close/toast.dart';
import 'package:flutter/material.dart';
import 'package:pet_store/widgets/guess_game_random_card.dart';
import 'webservice/API.dart';
import 'main.dart';
import 'dart:math';
import 'utils/utils.dart';
Random random = new Random();
class Guess_Game extends StatefulWidget {
const Guess_Game({Key? key}) : super(key: key);
#override
State<Guess_Game> createState() => _Guess_GameState();
}
class _Guess_GameState extends State<Guess_Game> {
void initState() {
super.initState();
init_random_url = randomly_select_URL();
GuessGameFuture = API.get_pets(init_random_url);
}
void requestAgain() {
setState(() {
init_random_url = randomly_select_URL();
GuessGameFuture = API.get_pets(init_random_url);
});
}
#override
Widget build(BuildContext context) {
init_random_url = randomly_select_URL();
return Scaffold(
body: Center(
child:
FutureBuilder<List<dynamic>>(
future: GuessGameFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<dynamic>? pet_data = snapshot.data;
var number_of_parameters = snapshot.data!.length;
var random_pet = random.nextInt(number_of_parameters);
var category = pet_data![random_pet].category.toString();
var photoURL = pet_data![random_pet].photoUrls;
// Here is the condition that ensure pet category is in the list and has an image
if (checkCategoryInList(category, items) &&
photoURL.length != 0) {
return Random_Card(
pet_data: pet_data,
random_pet: random_pet,
dropdownvalue: dropdownvalue);
} else {
if (photoURL.length == 0) {
print(" NO PHOTO SUBMITTED FOR THIS PET");
} else {
print(category + "NOT IN CATEGORY");
}
WidgetsBinding.instance.addPostFrameCallback((_) {
requestAgain();
});
}
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return const CircularProgressIndicator();
},
)
else
const Text(
"Please select your guess",
style: TextStyle(fontSize: 17, color: Colors.indigo),
),
),
),
}
}
Add this line to build
GuessGameFuture = API.get_pets(randomly_select_URL());
and Change requestAgain function to this:
void requestAgain() {
setState(() {
GuessGameFuture = API.get_pets(randomly_select_URL());
});
}
Also you can use FutureProvider and riverpod library.
Hope it helps

Cubit not emitting state

Folks, I need some help with this.
I have this code
void onLoad(int id) async {
final result = await ListRepository.loadProduct(id);
if (result != null) {
product = result;
WoocommerceModel? model = product!.woo;
if (model!.woocommerce) {
List? result2 = await ListRepository.loadOffsiteCoupon(model: model);
emit(ProductOffsiteCouponSuccess(result2));
emit(ProductDetailSuccess(product!));
} else {
emit(ProductDetailSuccess(product!));
}
}
}
emit(ProductDetailSuccess(product!)); works fine but emit(ProductOffsiteCouponSuccess(result2)); doesn't work.
Here's the state definition:
abstract class ProductDetailState {}
class ProductDetailLoading extends ProductDetailState {}
class ProductOffsiteCouponSuccess extends ProductDetailState {
final List? coupon;
ProductOffsiteCouponSuccess(this.coupon);
}
class ProductDetailSuccess extends ProductDetailState {
final ProductModel product;
ProductDetailSuccess(this.product);
}
I'm trying to access the data here >>
if (state is ProductOffsiteCouponSuccess) {
couponList = state.coupon;
}
In this build method >>
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => _productDetailCubit,
child: BlocBuilder<ProductDetailCubit, ProductDetailState>(
builder: (context, state) {
ProductModel? product;
List? couponList;
if (state is ProductDetailSuccess) {
product = state.product;
}
if (state is ProductOffsiteCouponSuccess) {
couponList = state.coupon;
}
return Scaffold(
body: _buildContent(product),
drawer: Drawer(),
);
},
),
);
}
}
Any idea why this doesn't work?
I get the data in the variable 'result2'with no issues, but I can't emit the success state to hold the data for later use.
Any help is appreciated.
Thanks in advance.
If you are using a specific type with coupon (like List) and it is not a String or int then you should extend it and also the abstract class with Equatable and add its variables to props even if this doesn't work then you can try toSet().toList()

How to cast to unknown generic runtime type (C# ChangeType equivalent)

I'm brand new to Flutter / Dart and I'm trying to build a reusable infinite scroller with placeholder loading. The class is as follows:
import 'dart:async';
import 'package:flutter/material.dart';
class PagedScroller<T> extends StatefulWidget {
final int limit;
final Future<List<T>> Function(int, int) getDataFunction;
final Widget Function(T) renderFunction;
final Widget Function() renderPlaceholderFunction;
PagedScroller(
{#required this.limit,
#required this.getDataFunction,
#required this.renderFunction,
#required this.renderPlaceholderFunction});
#override
_PagedScrollerState<T> createState() => _PagedScrollerState<T>();
}
class _PagedScrollerState<T> extends State<PagedScroller> {
int _offset = 0;
int _lastDataLength = 1; // Init to one so the first call can happen
List<dynamic> _items = [];
Future<List<dynamic>> _future;
bool _isInitializing = false;
bool _isInitialized = false;
bool _isLoading = false;
ScrollController _controller =
ScrollController(initialScrollOffset: 0.0, keepScrollOffset: true);
_PagedScrollerState();
void _init() {
_isInitializing = true;
_reset();
_controller.addListener(() {
bool loadMore = false;
if (_controller.position.maxScrollExtent == double.infinity) {
loadMore = _controller.offset == _controller.position.maxScrollExtent;
} else {
loadMore =
_controller.offset >= _controller.position.maxScrollExtent * 0.85;
}
// Only load more if it's not currently loading and we're not on the last page
// _lastDataLength should be 0 if there are no more pages
if (loadMore && !_isLoading && _lastDataLength > 0) {
_offset += widget.limit;
_load();
}
});
_load();
_isInitializing = false;
_isInitialized = true;
}
void _reset() {
// Clear things array and reset inital get-things link (without paging)
setState(() {
_future = _clearThings();
});
// Reload things
// Reset to initial GET link
_offset = 0;
}
void _load() {
setState(() {
_future = _loadPlaceholders();
_future = _loadData();
});
}
Future<List<dynamic>> _clearThings() async {
_items.clear();
return Future.value(_items);
}
Future<List<dynamic>> _loadPlaceholders() async {
// Add 20 empty placeholders to represent stuff that's currently loading
for (var i = 0; i < widget.limit; i++) {
_items.add(_Placeholder());
}
return Future.value(_items);
}
List<dynamic> _getInitialPlaceholders() {
var placeholders = List<dynamic>();
for (var i = 0; i < widget.limit; i++) {
placeholders.add(_Placeholder());
}
return placeholders;
}
Future<List<dynamic>> _loadData() async {
_setLoading(true);
var data = await widget.getDataFunction(widget.limit, _offset);
// When loading data is done, remove any placeholders
_items.removeWhere((item) => item is _Placeholder);
// If 0 items were returned, it's probably the last page
_lastDataLength = data.length;
for (var item in data) {
_items.add(item);
}
_setLoading(false);
return Future.value(_items);
}
void _setLoading(bool isLoading) {
if (!mounted) {
return;
}
setState(() {
_isLoading = isLoading;
});
}
Future<void> _refreshThings() async {
_reset();
_load();
return Future;
}
#override
Widget build(BuildContext context) {
if (!_isInitializing && !_isInitialized) {
_init();
}
return FutureBuilder(
future: _future,
initialData: _getInitialPlaceholders(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
List<dynamic> loadedItems = snapshot.data;
return RefreshIndicator(
onRefresh: _refreshThings,
child: ListView.builder(
itemCount: loadedItems.length,
controller: _controller,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
var item = loadedItems[index];
if (item is _Placeholder) {
return widget.renderPlaceholderFunction();
} else if (item is T) {
// THIS IS THE LINE THAT FAILS
return widget.renderFunction(item);
}
return Text('Unknown item type');
},
),
);
}
return Container();
},
);
}
}
class _Placeholder {}
The line that fails above:
return widget.renderFunction(item);
Fails with the following:
type '(MyModel) => Widget' is not a subtype of type '(dynamic) => Widget'
I understand why this is happening. The compiler can't know that type T from my PagedScroller<T> is the same as type T from _PagedScrollerState<T>. As a result, Dart tries to be helpful and converts my callback function of type Widget Function(T) to Widget Function(dynamic).
I then figured "maybe I can fake it out" with the following since I know the T in PagedScroller<T> and _PagedScrollerState<T> are always the same:
var renderFunction = widget.renderFunction as Widget Function(T);
return renderFunction(item);
Interestingly, this gives me a warning:
Unnecessary cast.
Try removing the cast.
Yet it won't even run that line (crashes) with the following:
Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=BUG.md
Changing everything to dynamic works a charm, but I really don't want to lose the readability of generics here if I don't have to.
Despite extensive searching, I can't find the equivalent of C#'s Convert.ChangeType where you can provide types at runtime so I can just do the cast I want and be done with it.
This seems like a really simple thing to achieve, but I'm stuck.
You can consume the scroller with this simple main.dart copy/pasted:
import 'package:flutter/material.dart';
import 'package:minimal_repros/paged_scroller.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
Future<List<MyModel>> getDataFunction(int limit, int offset) async {
var myModels = List<MyModel>();
// Simulate API call
await Future.delayed(Duration(milliseconds: 1000));
for (int i = 0; i < limit; i++) {
var myModel = MyModel();
myModel.count = i + offset;
myModel.firstName = 'Bob';
myModels.add(myModel);
}
return myModels;
}
Widget renderFunction(MyModel myModel) {
return Text(myModel.firstName);
}
Widget renderPlaceholderFunction() {
return Text('Loading');
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: PagedScroller(
getDataFunction: getDataFunction,
renderFunction: renderFunction,
renderPlaceholderFunction: renderPlaceholderFunction,
limit: 20));
}
}
class MyModel {
int count;
String firstName;
}
In the declaration of your State class, you forgot to specify the generic parameter of the widget.
Instead of:
class _PagedScrollerState<T> extends State<PagedScroller> {
do:
class _PagedScrollerState<T> extends State<PagedScroller<T>> {