My question concerns Riverpod.
I have a widget with 2 tabs. When i change the tab, i want to update a property (infoShare) with a "address" value if i choose the 1st tab and with a "publicKey" value if i choose the 2nd tab.
I used notifier to do that. No issue with that.
But when i instanciate the 1st time the main widget, my provider is not initialize.
So i need to fix it to create a specific provider to initialize it and use a ProviderScope.
Is it the good way to initialize my widget ?
Here's my code. It works but i don't know if it's the good solution.
And perhaps Riverpod's annotations could be helpfull
final _contactDetailInfoShareProviderArgs = Provider<Contact>(
(ref) {
throw UnimplementedError();
},
);
final _contactDetailInfoShareProvider = NotifierProvider.autoDispose
.family<ContactDetailInfoShareNotifier, String, Contact>(
() {
return ContactDetailInfoShareNotifier();
},
);
class ContactDetailInfoShareNotifier
extends AutoDisposeFamilyNotifier<String, Contact> {
ContactDetailInfoShareNotifier();
#override
String build(Contact arg) {
return arg.address.toUpperCase();
}
void setInfoShare(int tab, Contact contact) {
if (tab == 1) {
state = contact.publicKey.toUpperCase();
} else {
state = contact.address.toUpperCase();
}
}
}
abstract class ContactDetailProvider {
static final contactDetailInfoShare = _contactDetailInfoShareProvider;
static final contactDetailInfoShareProviderArgs =
_contactDetailInfoShareProviderArgs;
}
class ContactDetail extends ConsumerWidget {
const ContactDetail({
required this.contact,
super.key,
});
final Contact contact;
#override
Widget build(BuildContext context, WidgetRef ref) {
return ProviderScope(
overrides: [
ContactDetailProvider.contactDetailInfoShareProviderArgs
.overrideWithValue(
contact,
),
],
child: ContactDetailBody(
contact: contact,
),
);
}
}
class ContactDetailBody extends ConsumerWidget {
const ContactDetailBody({
required this.contact,
super.key,
});
final Contact contact;
#override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
children: <Widget>[
Expanded(
child: Container(
child: ContainedTabBarView(
tabs: [
Text('tab1'),
Text('tab2'),
],
views: [
ContactDetailTab1(),
ContactDetailTab2(),
],
onChange: (p0) {
ref
.watch(
ContactDetailProvider.contactDetailInfoShare(contact)
.notifier,
)
.setInfoShare(p0, contact);
},
),
),
),
AppButton(
'Share',
onPressed: () {
print(
ref.watch(
ContactDetailProvider.contactDetailInfoShare(
ref.read(
ContactDetailProvider.contactDetailInfoShareProviderArgs,
),
),
),
);
},
)
],
);
}
}
ProviderScope should not be used other than the root of your app, or just for testing. It is not designed to be used in your widgets. There are many other options to init your providers, consider using .family or using some StateNotifierProvider with three states loading, error, value, and you can have the initial value to be loading and after initialization you can have the value state. Or consider using new Riverpod code generation to have some more complex logic to init your provider.
Related
I would like to get the value of the dropdown from the other widget in the real estate app. Say I have two widgets. First one is the dropdown widget, and the second one is Add New Property widget (or a page).. I would like to access the value of the dropdown from the Add New Property.
I could achieve this with final Function onChanged; but Im wondering if there is another way to achieve with the Provider package or the ValueNotifier
the code below is my Dropdown button widget
class PropertyType extends StatefulWidget {
final Function onChanged;
const PropertyType({
super.key,
required this.onChanged,
});
#override
State<PropertyType> createState() => _PropertyTypeState();
}
class _PropertyTypeState extends State<PropertyType> {
final List<String> _propertyTypeList = propertyType;
String? _propertyType = 'No Info';
#override
Widget build(BuildContext context) {
return ANPFormContainer(
fieldTitle: 'Property Type',
subTitle: 'အိမ်ခြံမြေအမျိုးအစား',
child: FormBuilderDropdown<String>(
name: 'a2-propertyType',
initialValue: _propertyType,
items: _propertyTypeList
.map(
(itemValue) => DropdownMenuItem(
value: itemValue,
child: Text(itemValue),
),
)
.toList(),
onChanged: (val) {
setState(() {
_propertyType = val;
widget.onChanged(val);
});
},
),
);
}
}
And this is the "Add New Property" form page
class ANPTest extends StatefulWidget {
const ANPTest({super.key});
#override
State<ANPTest> createState() => _ANPTestState();
}
class _ANPTestState extends State<ANPTest> {
final TextEditingController _propertyid = TextEditingController();
String _propertyType = 'No Info';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ZayyanColorTheme.zayyanGrey,
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
PropertyID(propertyID: _propertyid),
PropertyType(onChanged: (String value) {
_propertyType = value;
}),
addVerticalSpacer(25),
ANPNextButton(onPressed: _onpressed),
],
),
),
);
}
_onpressed() {
final anp = MdlFirestoreData(
propertyid: _propertyid.text, propertyType: _propertyType)
.toFirestore();
FirebaseFirestore.instance.collection('Selling Posts').add(anp);
}
}
Thank you for helping me out.
Best
yes, you could use Getx or provider package by creating a controller(function) and the package helps you to have access to variables in
your controller to use them everywhere in your program,
you may need to learn about Getx
it can help you manage your navigation and state
The state here is maintained in a list of instances of Products called _shoppingCart
(The following code is an example from https://docs.flutter.dev/development/ui/widgets-intro#keys). The state is being mapped to widgets and every time a change is made to the list of products, all the widgets part of the list, regardless of being changed, still rebuild. Is this how it is supposed to be? or is there a better way?
import 'package:flutter/material.dart';
class Product {
const Product({required this.name});
final String name;
}
typedef CartChangedCallback = Function(Product product, bool inCart);
class ShoppingListItem extends StatelessWidget {
ShoppingListItem({
required this.product,
required this.inCart,
required this.onCartChanged,
}) : super(key: ObjectKey(product));
final Product product;
final bool inCart;
final CartChangedCallback onCartChanged;
Color _getColor(BuildContext context) {
// The theme depends on the BuildContext because different
// parts of the tree can have different themes.
// The BuildContext indicates where the build is
// taking place and therefore which theme to use.
return inCart //
? Colors.black54
: Theme.of(context).primaryColor;
}
TextStyle? _getTextStyle(BuildContext context) {
if (!inCart) return null;
return const TextStyle(
color: Colors.black54,
decoration: TextDecoration.lineThrough,
);
}
#override
Widget build(BuildContext context) {
print("rebuilding ${product.name}");
return ListTile(
onTap: () {
onCartChanged(product, inCart);
},
leading: CircleAvatar(
backgroundColor: _getColor(context),
child: Text(product.name[0]),
),
title: Text(
product.name,
style: _getTextStyle(context),
),
);
}
}
class ShoppingList extends StatefulWidget {
const ShoppingList({required this.products, super.key});
final List<Product> products;
// The framework calls createState the first time
// a widget appears at a given location in the tree.
// If the parent rebuilds and uses the same type of
// widget (with the same key), the framework re-uses
// the State object instead of creating a new State object.
#override
State<ShoppingList> createState() => _ShoppingListState();
}
class _ShoppingListState extends State<ShoppingList> {
final _shoppingCart = <Product>{};
void _handleCartChanged(Product product, bool inCart) {
setState(() {
// When a user changes what's in the cart, you need
// to change _shoppingCart inside a setState call to
// trigger a rebuild.
// The framework then calls build, below,
// which updates the visual appearance of the app.
if (!inCart) {
_shoppingCart.add(product);
} else {
_shoppingCart.remove(product);
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Shopping List'),
),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 8.0),
children: widget.products.map((product) {
return ShoppingListItem(
//key: ObjectKey(product),
product: product,
inCart: _shoppingCart.contains(product),
onCartChanged: _handleCartChanged,
);
}).toList(),
),
);
}
}
void main() {
runApp(const MaterialApp(
title: 'Shopping App',
home: ShoppingList(
products: [
Product(name: 'Eggs'),
Product(name: 'Flour'),
Product(name: 'Chocolate chips'),
],
),
));
}
I think you don't need to worry about this, keep using const or value keys where you think that these widgets will not be changed,
Otherwise let Flutter framework handle this,
Flutter framework is smart enough, during setState it will only update the element tree, will not create/paint it from start,
So only updated elements will be repainted rest will be there.
Apologies in advance for posting Pseudo code. Real code would be too long.
I have a screen where I have a drop down at the top where a user can select an option. The rest of the page updates based on that option. Something like this:
// state variable
String idFromDropdown;
Column(
children: [
DropDownWidget(),
ChildWidget1(myId: idFromDropDown),
ChildWidget2(myId: idFromDropDown),
ChildWidget3(myId: idFromDropDown),
]
)
In the child widgets, I am using widget.myId to pass into a backend service and read new data.
Expectation is that when the dropdown changes and I call
setState((val)=>{idFromDropdown = val});
then the value would cascade into the three child widgets. Somehow trigger the widgets to reconnect to the backend service based on the new value of widget.myId.
How do I trigger a state update on the child widgets?
I ended up using a ValueNotifier. Instead of directly using a string and passing that into the child widgets. I ended up doing something like:
ValueNotifier<String> idFromDropdown;
...
setState((val)=>{idFromDropdown.value = val});
Then in each widget, I am adding a listener onto the ValueNotifier coming in and retriggering the read to the backend service.
While this works, I feel like I'm missing something obvious. My childwidgets now take in a ValueNotifier instead of a value. I'm afraid this is going to make my ChildWidgets more difficult to use in other situations.
Is this the correct way of doing this?
Use provider package your problem will solved easily
Here is example of Riverpod.
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
final fetureDataForChild =
FutureProvider.family<List<String>, String>((ref, id) {
return Future.delayed(Duration(seconds: 1), () {
return <String>["active", "${id}", "got it "];
});
});
class MainWidgetR extends StatefulWidget {
#override
_MainWidgetState createState() => _MainWidgetState();
}
class _MainWidgetState extends State<MainWidgetR> {
String id = "id 0";
final items = List.generate(
4,
(index) => DropdownMenuItem(
child: Text("Company $index"),
value: "id $index",
),
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DropdownButton(
items: items,
value: id,
onChanged: (value) {
setState(() {
id = value as String;
});
},
),
RiverPodRespone(
id: id,
),
],
),
),
);
}
}
class RiverPodRespone extends ConsumerWidget {
final String id;
RiverPodRespone({
required this.id,
});
#override
Widget build(BuildContext context, watch) {
final futureData = watch(fetureDataForChild("$id"));
return futureData.map(
data: (value) {
final items = value.value;
return Column(
children: [
...items.map((e) => Text("$e")).toList(),
],
);
},
loading: (value) => CircularProgressIndicator(),
error: (value) => Text(value.toString()),
);
}
}
when I use Getx to update my Widget?
I do not know Rx() how to contact to the thing I put in.
code is _obx=Rx().
but I send data is "".obs. that is not Rx() but this is RxString().
when I use "".obs.value="newString". why Rx() can know that who updates data.
just like :
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class GetIncrementPage extends StatefulWidget {
GetIncrementPage({Key key}) : super(key: key);
#override
_GetIncrementPageState createState() => _GetIncrementPageState();
}
class _GetIncrementPageState extends State<GetIncrementPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('get'),
),
body: Container(
alignment: Alignment.center,
child: _body(),
),
);
}
Widget _body() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
OutlineButton(
child: Text('get 数字加减'),
onPressed: c.increment,
),
OutlineButton(
child: Text('get log 变化'),
onPressed: c.change,
),
Obx(() {
printInfo(info: '刷新了页面 get_example');
return Text(c.count.toString());
}),
ObxValue((v) {
printInfo(info: '刷新了页面 get_ObxValue_log1 ');
return Text('logValue:' + v.toString());
}, ObjectKey('key').obs),
Obx(() {
printInfo(info: '刷新了页面 get_obx_log1');
return Text('logObx:' + c.log.toString());
}),
Obx(() {
printInfo(info: '刷新了页面 get_obx_log2');
return Text(c.log2.toString());
}),
// ObxValue((var value) => Text('${value.toString()}'), c),
],
);
}
#override
void dispose() {
Get.delete<Controller2>();
super.dispose();
}
final Controller2 c = Get.put(Controller2());
}
///
/// Created by fgyong on 2020/10/22.
///
class Controller2 extends GetxController {
var count = 0.obs;
var count2 = 0.obs;
final log = ''.obs;
final log2 = ''.obs;
increment() => count++;
#override
void onClose() {
printInfo(info: 'Controller close');
super.onClose();
}
void change() {
log.value += ' ${log.value.length}';
}
}
when i change log.value to new String,why log2 do not fresh.
class Obx extends StatefulWidget {
final WidgetCallback builder;
const Obx(this.builder);
_ObxState createState() => _ObxState();
}
class _ObxState extends State<Obx> {
RxInterface _observer;
StreamSubscription subs;
_ObxState() {
_observer = Rx();
}
#override
void initState() {
subs = _observer.subject.stream.listen((data) => setState(() {}));
super.initState();
}
#override
void dispose() {
subs.cancel();
_observer.close();
super.dispose();
}
Widget get notifyChilds {
final observer = getObs;
getObs = _observer;
final result = widget.builder();
if (!_observer.canUpdate) {
throw """
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables into GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
""";
}
getObs = observer;
return result;
}
#override
Widget build(BuildContext context) => notifyChilds;
}
Why can rx() establish contact with the log, please help me. When I update
How can Rx() know when logging?
just help me.
You can use Obx or GetX widgets from Get to "listen" to changes to observable variables you declare in a GetxController.
I think you are also confusing Rx as an ObserVER vs. ObservABLE. Rx is an observable, i.e. you watch it for changes using Obx or GetX widgets, (I guess you can call these two widgets "Observers".)
Basic Example
class Log2Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
Controller c = Get.put(Controller());
// ↑ declare controller inside build method
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Obx(
() => Text('${c.log2.value}')
),
RaisedButton(
child: Text('Add +1'),
onPressed: c.change,
)
],
),
),
),
);
}
}
class Controller extends GetxController {
RxInt log2 = 0.obs;
void change() => log2.value++;
}
You likely don't need a StatefulWidget when using GetX. A GetxController lives outside the lifecycle of widgets. State is stored in a GetX Controller (instead of in a StatefulWidget).
GetX takes care of streams & subscriptions through variables you declare as obs, like count.obs and log2.obs. When you want to "listen" or "observe", use Obx or GetX widgets. These automatically listen to obs changes of its child and rebuild when it changes.
Obx vs. GetBuilder vs. GetX
class Log2Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
Controller c = Get.put(Controller());
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Obx(
() => Text('Obx: ${c.log2.value}')
),
// ↓ requires manual controller.update() call
GetBuilder<Controller>(
builder: (_c) => Text('GetBuilder: ${_c.log2.value}'),
),
// ↓ controller instantiated by Get widget
GetX<Controller>(
init: Controller(),
builder: (_c) => Text('GetX: ${_c.log2.value}'),
),
RaisedButton(
child: Text('Add +1'),
onPressed: c.change,
),
RaisedButton(
child: Text('Update GetBuilder'),
onPressed: c.update, // rebuild GetBuilder widget
),
],
),
),
),
);
}
}
class Controller extends GetxController {
RxInt log2 = 0.obs;
void change() => log2.value++;
}
Obx
Listens to observable (obs) changes. Controller needs to already be declared/initialized elsewhere to use.
GetX
Listens to observable (obs) changes. Can initialize controller itself using init: constructor argument, if not done elsewhere. Optional argument. Safe to use init: if Controller already instantiated. Will connect to existing instance.
GetBuilder
Does not listen to obs changes. Must be rebuilt manually by you, calling controller.update(). Similar to a setState() call. Can initialize controller itself using init: argument, if not done elsewhere. Optional.
First:
when I "".obx.value="newString".why Rx() can know.
This is wrong, the .obx doesn't exist, I guess you mean .obs;
When you create a OBS variable like: final a = ''.obs, the type of this var will be a RxString(), so you can use to observer this var whatever you want to.
I know two widgets can you use to observer in your screen:
GetX(), Obx()
see link https://github.com/jonataslaw/getx/issues/937,
when Obx() build,we named it ObxA, named "ABC".obs abcobs,
in Obx
Widget get notifyChilds {
final observer = getObs;
getObs = _observer;
final result = widget.builder();
if (!_observer.canUpdate) {
throw """
[Get] the improper use of a GetX has been detected.
You should only use GetX or Obx for the specific widget that will be updated.
If you are seeing this error, you probably did not insert any observable variables into GetX/Obx
or insert them outside the scope that GetX considers suitable for an update
(example: GetX => HeavyWidget => variableObservable).
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
""";
}
getObs = observer;
return result;
}
when build,RxString() will execute get value,and addListen():
code is
set value(T val) {
if (_value == val && !firstRebuild) return;
firstRebuild = false;
_value = val;
subject.add(_value);
}
/// Returns the current [value]
T get value {
if (getObs != null) {
getObs.addListener(subject.stream);
}
return _value;
}
void addListener(Stream<T> rxGetx) {
if (_subscriptions.containsKey(rxGetx)) {
return;
}
_subscriptions[rxGetx] = rxGetx.listen((data) {
subject.add(data);
});
}
so They made a connection
I am using flutter_bloc library to create a verification code page. Here is what I tried to do.
class PhonePage extends StatelessWidget {
static Route route() {
return MaterialPageRoute<void>(builder: (_) => PhonePage());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
create: (_) =>
ValidationCubit(context.repository<AuthenticationRepository>()),
child: PhoneForm(),
),
);
}
}
class PhoneForm extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocConsumer<ValidationCubit, ValidationState>(
listener: (context, state) {
print('Listener has been called');
if (state.status.isSubmissionFailure) {
_showVerificationError(context);
} else if (state.status.isSubmissionSuccess) {
_showVerificationSuccess(context);
}
},
builder: (context, state) {
return Container(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_HeaderAndTitle(),
_VerificationInput(),
_VerifyButton()
],
),
),
),
);
},
);
}
void _showVerificationError(context) {
Scaffold.of(context)
..hideCurrentSnackBar()
..showSnackBar(const SnackBar(content: Text('Validation error')));
}
void _showVerificationSuccess(context) {
Scaffold.of(context)
..hideCurrentSnackBar()
..showSnackBar(const SnackBar(
content: Text('Validation Success'),
backgroundColor: Colors.green,
));
}
}
...
class _VerifyButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ValidationCubit, ValidationState>(
builder: (context, state) {
return RaisedButton.icon(
color: Colors.blue,
padding: EdgeInsets.symmetric(horizontal: 38.0, vertical: 12.0),
textColor: Colors.white,
icon: state.status.isSubmissionInProgress
? Icon(FontAwesomeIcons.ellipsisH)
: Icon(null),
label: Text(state.status.isSubmissionInProgress ? '' : 'Verify',
style: TextStyle(fontSize: 16.0)),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
onPressed: state.status.isValid
? () => context.bloc<ValidationCubit>().verifyCode()
: null);
});
}
}
Now the verifyCode() function is an async function defined inside ValidationCubit. It emits states with status set to loading, success and failure. However the listener doesn't pick up those changes and show the snackbars. I couldn't figure why? I am also using the Formz library as suggested in the flutter_bloc documentation. Here is the verifyCode part.
Future<void> verifyCode() async {
if (!state.status.isValidated) return;
emit(state.copyWith(status: FormzStatus.submissionInProgress));
try {
// send validation code to server here
await _authenticationRepository.loginWithEmailAndPassword(
email: 'email#email.com', password: '12');
emit(state.copyWith(status: FormzStatus.submissionSuccess));
} on Exception {
emit(state.copyWith(status: FormzStatus.submissionFailure));
}
}
The verification code model looks like this:
class ValidationState extends Equatable {
final VerificationCode verificationCode;
final FormzStatus status;
const ValidationState({this.verificationCode, this.status});
#override
List<Object> get props => [verificationCode];
ValidationState copyWith(
{VerificationCode verificationCode, FormzStatus status}) {
return ValidationState(
verificationCode: verificationCode ?? this.verificationCode,
status: status ?? this.status);
}
}
And the validation state class is:
class ValidationState extends Equatable {
final VerificationCode verificationCode;
final FormzStatus status;
const ValidationState({this.verificationCode, this.status});
#override
List<Object> get props => [verificationCode];
ValidationState copyWith(
{VerificationCode verificationCode, FormzStatus status}) {
return ValidationState(
verificationCode: verificationCode ?? this.verificationCode,
status: status ?? this.status);
}
}
I think the problem is your state class.
listener is only called once for each state change (NOT including the initial state) unlike builder in BlocBuilder and is a void function.
Every time when a new state is emitted by the Cubit it is compared with the previous one, and if they are "DIFFERENT", the listener function is triggered.
In you situation, you are using Equatable with only verificationCode as props, which means when two states are compared only the verificationCodes are tested. In this way BLoC consumer thinks that the two states are equal and do not triggers the listener function again.
If you check your verifyCode() function the only changing parameter is status.
In order to fix that add the status property to the props list in your state class.
#override
List<Object> get props => [this.verificationCode, this.status];
if you want to update same state just add one state before calling your updating state
like this
if you want to update 'Loaded' state again call 'Loading' state before that and than call 'Loaded' state so BlocListener and BlocBuilder will listen to it
Edited
I have changed using bloc to cubit for this and cubit can emmit same state continuously and bloclistener and blocbuilder can listen to it
I needed to delete a list item from the list and app should pops a pop-up before delete. Somehow you decline the delete pop up via no button or click outside of the pop-up, last state doesn't change. After that, if you want to delete same item, it wasn't trigger cause all parameters are same with the previous state and equatable says its same. To get rid of this issue, you need to define a rand function and put just before your emit state. You need to add a new parameter to your state and you need to add to the props. It works like a charm.
My state;
class SomeDeleteOnPressedState extends SomeState
with EquatableMixin {
final int index;
final List<Result> result;
final String currentLocation;
final int rand;/// this is the unique part.
SomeDeleteOnPressedState({
required this.index,
required this.result,
required this.currentLocation,
required this.rand,
});
// don't forget to add rand parameter in props. it will make the difference here.
#override
List<Object> get props => <Object>[index, result, currentLocation, rand];
}
and my bloc;
on<SomeDeleteEvent>((event,emit){
int rand =Random().nextInt(100000);
emit(
SomeDeleteOnPressedState(
currentLocation: event.currentLocation,
index: event.index,
result: event.result,
rand: rand,/// every time it will send a different rand, so this state is always will be different.
),
);
});
Hope it helps.