Null check operator used on a null value - inside row->column->text - flutter

i am learnig flutter and here i was trying to use TextOverFLow.elipsis in a Design flow following=> listvie.seprated=> item builder=> Row=> container,Column=>text,text and i got 'Null check operator used on a null value',
ps;'it's my first time asking a que. here so pardon me if i wrote something wrong
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:sizer/sizer.dart';
import 'news_api_get_res_model.dart';
class NewsHomeScreen extends StatelessWidget {
const NewsHomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<NewsApiGetResModel>(
future: getNewsData(),
builder: (context, AsyncSnapshot<NewsApiGetResModel> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView.separated(
itemCount: snapshot.data!.articles!.length,
itemBuilder: (context, index) {
final data = snapshot.data!.articles![index];
return Padding(
padding: EdgeInsets.symmetric(
horizontal: 5.w,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
data.urlToImage == null
? Container(
height: 30.w,
width: 30.w,
child: Image(
fit: BoxFit.cover,
image: NetworkImage(
'https://i.pinimg.com/564x/0f/37/10/0f37109d0cc005766e5f9e625467d884.jpg')),
)
: Container(
height: 30.w,
width: 30.w,
child: Image(
fit: BoxFit.cover,
image: NetworkImage('${data.urlToImage}'))),
Flexible(
flex: 1,
child: Container(
child: Column(
children: [
Expanded(
child: Text(
"${data.title}",
overflow: TextOverflow.ellipsis,
),
),
Expanded(
child: Text(
"${data.title}",
overflow: TextOverflow.ellipsis,
),
),
],
),
),
)
],
),
);
},
separatorBuilder: (context, index) => Divider(thickness: 2),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
Future<NewsApiGetResModel> getNewsData() async {
http.Response response = await http.get(Uri.parse(
'https://newsapi.org/v2/everything?q=tesla&from=2022-10-11&sortBy=publishedAt&apiKey=6c5a2cd029a44eb186c8640325bd2901'));
var result = jsonDecode(response.body);
NewsApiGetResModel newsApiGetResModel = NewsApiGetResModel.fromJson(result);
log('${newsApiGetResModel.status}');
return newsApiGetResModel;
}
hi ,i am learning flutter and here i was trying to use TextOverFLow.elipsis in a Design flow following=> listvie.seprated=> item builder=> Row=> container,Column=>text,text and i got 'Null check operator used on a null value',
ps;'it's my first time asking a que. here so pardon me if i wrote something wrong

data is getting null. It is safe to check null first then use !.
return ListView.separated(
itemCount: snapshot.data?.articles?.length,
itemBuilder: (context, index) {
final data = snapshot.data?.articles?[index];
if(data == null) return Text("Got null");
return Padding(
padding: EdgeInsets.symmetric(
More about null-safety

Related

RangeError (index): Invalid value: Valid value range is empty: 0,,,can anyone tell me what is the problem in my streambuilder method?

Here is the error:
Exception caught by widgets library ═══════════════════════════════════
The following RangeError was thrown building StreamBuilder\<QuerySnapshot\<Object?\>\>(dirty, state: \_StreamBuilderBaseState\<QuerySnapshot\<Object?\>, AsyncSnapshot\<QuerySnapshot\<Object?\>\>\>#5a321):
RangeError (index): Invalid value: Valid value range is empty: 0
Here is my code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:emart_app/Controller/auth_controller.dart';
import 'package:emart_app/Controller/profile_controller.dart';
import 'package:emart_app/Services/firestore_services.dart';
import 'package:emart_app/consts/consts.dart';
import 'package:emart_app/consts/list.dart';
import 'package:emart_app/views/Auth%20Screen/login_screen.dart';
import 'package:emart_app/views/Profile%20Screen/Components(Profile)/details_card.dart';
import 'package:emart_app/views/Profile%20Screen/Components(Profile)/edit_profile_screen.dart';
import 'package:emart_app/widgets_common/bg_widget.dart';
import 'package:get/get.dart';
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
#override
Widget build(BuildContext context) {
var controller = Get.put(ProfileController());
return bgWidget(
child: Scaffold(
body: StreamBuilder(
stream: FirestoreServies.getUser(currentUser!.uid),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(redColor),
),
);
} else {
var data = snapshot.data!.docs[0];
return SafeArea(
child: Column(
children: [
//edit profile section
Padding(
padding: const EdgeInsets.all(8.0),
child: const Align(
alignment: Alignment.topRight,
child: Icon(
Icons.edit,
color: whiteColor,
)).onTap(() {
controller.nameController.text = data['name'];
controller.passController.text = data['password'];
Get.to(() => EditProfileScreen(data: data));
}),
),
//user details section
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
children: [
data['imageUrl'] == ''
? Image.asset(
imgProfile3,
width: 60,
fit: BoxFit.cover,
)
.box
.roundedFull
.clip(Clip.antiAlias)
.make()
: Image.network(
data['imageUrl'],
width: 60,
fit: BoxFit.cover,
)
.box
.roundedFull
.clip(Clip.antiAlias)
.make(),
20.widthBox,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"${data['name']}"
.text
.fontFamily(semibold)
.white
.make(),
"${data['email']}".text.white.make()
],
)),
OutlinedButton(
style: OutlinedButton.styleFrom(
side:
const BorderSide(color: whiteColor)),
onPressed: () async {
await Get.put(AuthController())
.signOutMethod(context);
Get.offAll(() => const LoginScreen());
},
child: logout.text
.fontFamily(semibold)
.white
.make())
],
),
),
10.heightBox,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
detailsCard(
count: data['cart_count'],
title: "In Your Cart",
width: context.screenWidth / 3.4),
detailsCard(
count: data['wishlist_count'],
title: "In Your Wishlist",
width: context.screenWidth / 3.4),
detailsCard(
count: data['order_count'],
title: "Your Orders",
width: context.screenWidth / 3.4)
],
),
5.heightBox,
ListView.separated(
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
leading: Image.asset(
profileButtonIcons[index],
width: 22,
),
title: profileButtonsList[index]
.text
.fontFamily(semibold)
.color(darkFontGrey)
.make(),
);
},
separatorBuilder: (context, index) {
return const Divider(
color: lightGrey,
);
},
itemCount: profileButtonsList.length)
.box
.rounded
.white
.margin(const EdgeInsets.all(12))
.padding(const EdgeInsets.symmetric(horizontal: 16))
.shadowSm
.make()
.box
.color(redColor)
.make()
],
));
}
})));
}
}
snapshot.data!.docs seems empty.
You can simply loop over it using for loop. and specify inside the index. snapshot.hasData checks if the value is non-null only, it does not check if it is empty or not.
if(snapshot.data!.docs.isNotEmpty){
for(int i=0; i<snapshot.data!.docs; i++){
final data = snapshot.data!.docs[i];
return SafeArea(child: AnyWidget(data: data));
}
} else {
return Text("List is empty");
}

How to wait for a request to complete using ObservableFuture?

When I transition to a screen where I get a list of information via an API, it initially gives an error:
_CastError (Null check operator used on a null value)
and only after loading the information, the screen is displayed correctly.
I am declaring the variables like this:
#observable
ObservableFuture<Model?>? myKeys;
#action
getKeys() {
myKeys = repository.getKeys().asObservable();
}
How can I enter the page only after loading the information?
In button action I tried this but to no avail!
await Future.wait([controller.controller.getKeys()]);
Modular.to.pushNamed('/home');
This is the page where the error occurs momentarily, but a short time later, that is, when the api call occurs, the data appears on the screen.
class MyKeyPage extends StatefulWidget {
const MyKeyPage({Key? key}) : super(key: key);
#override
State<MyKeyPage> createState() => _MyKeyPageState();
}
class _MyKeyPageState
extends ModularState<MyKeyPage, KeyController> {
KeyController controller = Modular.get<KeyController>();
Widget countKeys() {
return FutureBuilder(
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
final count =
controller.myKeys?.value?.data!.length.toString();
if (snapshot.connectionState == ConnectionState.none &&
!snapshot.hasData) {
return Text('..');
}
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: 1,
itemBuilder: (context, index) {
return Text(count.toString() + '/5');
});
},
future: controller.getCountKeys(),
);
}
#override
Widget build(BuildContext context) {
Size _size = MediaQuery.of(context).size;
return controller.getCountKeys() != "0"
? TesteScaffold(
removeHorizontalPadding: true,
onBackPressed: () => Modular.to.navigate('/exit'),
leadingIcon: ConstantsIcons.trn_arrow_left,
title: '',
child: Container(
width: double.infinity,
child: Padding(
padding: const EdgeInsets.only(left: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Keys',
style: kHeaderH3Bold.copyWith(
color: kBluePrimaryTrinus,
),
),
countKeys(),
],
),
),
),
body: Observer(builder: (_) {
return Padding(
padding: const EdgeInsets.only(bottom: 81),
child: Container(
child: ListView.builder(
padding: EdgeInsets.only(
left: 12.0,
top: 2.0,
right: 12.0,
),
itemCount:
controller.myKeys?.value?.data!.length,
itemBuilder: (context, index) {
var typeKey = controller
.myKeys?.value?.data?[index].type
.toString();
var id =
controller.myKeys?.value?.data?[index].id;
final value = controller
.myKeys?.value?.data?[index].value
.toString();
return GestureDetector(
onTap: () {
.
.
},
child: CardMeyKeys(
typeKey: typeKey,
value: value!.length > 25
? value.substring(0, 25) + '...'
: value,
myKeys: pixController
.minhasChaves?.value?.data?[index].type
.toString(),
),
);
},
),
),
);
}),
bottomSheet: ....
)
: TesteScaffold(
removeHorizontalPadding: true,
onBackPressed: () => Modular.to.navigate('/exit'),
leadingIcon: ConstantsIcons.trn_arrow_left,
title: '',
child: Container(
width: double.infinity,
child: Padding(
padding: const EdgeInsets.only(left: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'...',
style: kHeaderH3Bold.copyWith(
color: kBluePrimaryTrinus,
),
),
],
),
),
),
body: Padding(
padding: const EdgeInsets.only(bottom: 81),
child: Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/images/Box.png',
fit: BoxFit.cover,
width: 82.75,
height: 80.91,
),
SizedBox(
height: 10,
),
],
),
), //Center
),
),
bottomSheet: ...
);
}
List<ReactionDisposer> disposers = [];
#override
void initState() {
super.initState();
controller.getKeys();
}
#override
void dispose() {
disposers.forEach((toDispose) => toDispose());
super.dispose();
}
}
Initially the error occurs in this block
value: value!.length > 25
? value.substring(0, 25) + '...'
: value,
_CastError (Null check operator used on a null value)
I appreciate if anyone can help me handle ObservableFuture correctly!
You need to call the "future" adding
Future.wait
(the return type of getKeys) keys=await Future.wait([
controller.getKeys();
]);
The problem is your getKeys function isn't returning anything, so there's nothing for your code to await. You need to return a future in order to await it.
Future<Model?> getKeys() {
myKeys = repository.getKeys().asObservable();
return myKeys!; // Presumably this isn't null anymore by this point.
}
...
await controller.controller.getKeys();
Modular.to.pushNamed('/home');

How to fix Image overflow in Flutter PageView?

I'm building an app where it shows the title, author name, number of upvotes, and an image from the subreddit in a page view. Everything is working fine but for some images, the page view is overflowing, how do I fix this?
Here's the overflow error:
Here's my code
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class Stardew extends StatefulWidget {
const Stardew({ Key? key }) : super(key: key);
#override
State<Stardew> createState() => _StardewState();
}
class _StardewState extends State<Stardew> {
List data = [];
Future<String> getData() async {
List temp_data = [];
var response = await http.get(
Uri.parse("https://m...content-available-to-author-only...p.com/gimme/stardewvalley/100")
);
return response.body;
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot snapshot){
if(snapshot.data == null){
return Center(child: CircularProgressIndicator(color: Color(0xff008b00)));
}
var jsonData = jsonDecode(snapshot.data);
jsonData = jsonData["memes"];
return PageView.builder(
//scrollDirection: Axis.vertical,
itemCount: jsonData.length,
itemBuilder: (BuildContext context, int index){
return Center(
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
getImgCard(
jsonData[index]["title"],
//jsonData[index]["preview"][2],//preview image
jsonData[index]["url"], //original image
jsonData[index]["author"],
(jsonData[index]["ups"]).toString()
)
],
),
),
);
},
);
}
);
}
Widget getImage(String imgUrl){
return Container(
child: Image.network(
imgUrl,
fit: BoxFit.scaleDown,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded/loadingProgress.expectedTotalBytes! : null,
color: Color(0xff008b00),
),
);
},
),
);
}
Widget getImgCard(String title, String imgUrl, String author, String ups){
return Card(
color: Color(0xff000000),
clipBehavior: Clip.antiAlias,
child: Column(
children: [
ListTile(
leading: RichText(
text: TextSpan(
children: [
TextSpan(
text: ups,
),
const WidgetSpan(
child: Icon(Icons.arrow_upward, size: 18, color: Color(0xff008b00),),
)
],
),
),
title: Text(title, style: TextStyle(color: Colors.white),),
subtitle: Text(
"Posted by u/${author}",
style: TextStyle(color: Colors.white.withOpacity(0.6)),
),
),
getImage(imgUrl),
Padding(padding: EdgeInsets.only(bottom: 8))
],
),
);
}
}
How do I fix this? I have tried changing the box fit and it did not work. Then I used expaned and flexible widgets and still can't find the answer to this solution. please help me.
Wrap getImage(imgUrl) inside Expanded widget.
I found the answer myself, removing the parent column and wrapping it with SingleChildScrollView fixed the error.
return PageView.builder(
//scrollDirection: Axis.vertical,
itemCount: jsonData.length,
itemBuilder: (BuildContext context, int index){
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(1.0),
child: getImgCard(
jsonData[index]["title"],
//jsonData[index]["preview"][2],//preview image
jsonData[index]["url"], //original image
jsonData[index]["author"],
(jsonData[index]["ups"]).toString()
),
)
);
},
);

Flutter page jumps to top after setState({})

I display many images in a Staggered Gridview in a Flutter application.
Everytime I call setState({}), for example after deleting an item, the page jumps to top. How could I remove this behavior?
This is my code:
final _scaffoldKey = new GlobalKey<ScaffoldState>();
.. outside the build function. And then...
return loadingScreen == true
? LoadingScreen()
: Scaffold(
key: _scaffoldKey,
body: CustomScrollView(
slivers: <Widget>[
_AppBar(
theme: theme,
index: index,
albumImagePath: albumImagePath,
albumID: albumID,
albumValue: albumValue,
addPictureToGallery: _addPictureToGallery,
),
SliverToBoxAdapter(
child: Column(
children: <Widget>[
InfoBar(
albumPicturesSum: albumPicturesSum,
getBilderString: _getBilderString,
theme: theme,
getVideoProgress: _getVideoProgress,
progress: progress,
),
albumID == 99999999
? // Demo Projekt
DemoImageGrid(
demoImageList: demoImageList,
getDemoImagesJson: _getDemoImagesJson,
)
: UserImageGrid(
picturesData: picturesData,
albumID: albumID,
showPictureActions: _showPictureActions)
],
),
)
],
),
);
}
The UserImageGrid looks like the following:
class UserImageGrid extends StatelessWidget {
final Pictures picturesData;
final int albumID;
final Function showPictureActions;
final _key = new UniqueKey();
UserImageGrid(
{#required this.picturesData,
#required this.albumID,
#required this.showPictureActions});
#override
Widget build(BuildContext context) {
return FutureBuilder(
key: _key,
future: picturesData.getPicturesFromAlbum(albumID),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// Normale Projekte
if (snapshot.hasData && snapshot.data.length == 0) {
return Center(
child: Column(
children: <Widget>[
Lottie.asset('assets/lottie/drone.json',
width: 250,
options: LottieOptions(enableMergePaths: false)),
],
),
);
}
if (!snapshot.hasData ||
snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Container(
child: StaggeredGridView.countBuilder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsets.all(0),
crossAxisCount: 6,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) =>
GestureDetector(
onLongPress: () {
showPictureActions(snapshot.data[index]);
},
onTap: () async {
await showDialog(
context: context,
builder: (_) {
return Dialog(
child: Stack(
children: [
Container(
margin: const EdgeInsets.symmetric(
vertical: 10.0,
horizontal: 10.0,
),
height: 500.0,
child: ClipRect(
child: PhotoView(
maxScale:
PhotoViewComputedScale.covered * 2.0,
minScale:
PhotoViewComputedScale.contained *
0.8,
initialScale:
PhotoViewComputedScale.covered,
imageProvider: FileImage(
File(snapshot.data[index].path))),
),
),
Positioned(
bottom: 20,
left: 20,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
DateFormat(tr("date_format")).format(
snapshot.data[index].timestamp
.toDateTime()),
style: TextStyle(color: Colors.white),
),
),
)
],
));
});
},
child: Container(
child: Image.file(
File(snapshot.data[index].thumbPath),
fit: BoxFit.cover,
)),
),
staggeredTileBuilder: (int index) =>
new StaggeredTile.count(2, index.isEven ? 2 : 2),
mainAxisSpacing: 5.0,
crossAxisSpacing: 5.0,
),
);
}
});
}
}
What could be the issue?
I found a solution for this issue. The problem was not the setState({}). It was the return Widget of the FutureBuilder.
I changed
if (!snapshot.hasData || snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
to:
if (!snapshot.hasData || snapshot.connectionState == ConnectionState.waiting) {
return Container(
height: MediaQuery.of(context).size.height,
);
}
I don´t exactly know why, but with this change the page is not jumping to top anymore on setState({})

Adjust Z-Index in flutter

I try to place an element above another element in flutter. With transform: Matrix4.translationValues it worked to set a negative value, but the element above has a bigger z-index. How could I adjust that? To understand what I need:
This is what I have
This is what I need
My code
class _AlbumDetailState extends State<AlbumDetail> {
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final routeArgs =
ModalRoute.of(context).settings.arguments as Map<String, int>;
final albumID = routeArgs['id'];
final index = routeArgs['index'];
final picturesData = Provider.of<Pictures>(context, listen: true);
Future<void> _addPictureToGallery() async {
final picker = ImagePicker();
final imageFile =
await picker.getImage(source: ImageSource.gallery, maxWidth: 600);
final appDir = await syspath.getApplicationDocumentsDirectory();
final fileName = path.basename(imageFile.path);
final savedImage =
await File(imageFile.path).copy('${appDir.path}/$fileName');
print(savedImage);
picturesData.add(Picture(
album: albumID, path: savedImage.path, timestamp: Timestamp.now()));
}
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Text("Album"),
flexibleSpace: FlexibleSpaceBar(
background: Container(
color: Colors.transparent,
child: Hero(
tag: "open_gallery" + index.toString(),
child: Image(
image: NetworkImage('https://placeimg.com/640/480/any'),
fit: BoxFit.cover,
),
),
)),
expandedHeight: 350,
backgroundColor: Colors.green,
pinned: true,
stretch: false,
),
SliverToBoxAdapter(
child: FutureBuilder(
future: picturesData.getPicturesFromAlbum(albumID),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData && snapshot.data.length == 0) {
return Center(
child: Text("Noch keine Bilder vorhanden"),
);
}
if (!snapshot.hasData ||
snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
alignment: Alignment.center,
transform: Matrix4.translationValues(0.0, -75.0, 0.0),
width: MediaQuery.of(context).size.width - 50,
height: 150,
color: Colors.black87,
margin: EdgeInsets.only(top: 50),
child: Text(
"Headline",
style: Theme.of(context)
.textTheme
.headline2
.copyWith(color: theme.colorScheme.onPrimary),
),
),
StaggeredGridView.countBuilder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
crossAxisCount: 6,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) =>
Container(
child: Image.file(
File(snapshot.data[index].path),
fit: BoxFit.cover,
)),
staggeredTileBuilder: (int index) =>
new StaggeredTile.count(2, index.isEven ? 2 : 1),
mainAxisSpacing: 5.0,
crossAxisSpacing: 5.0,
),
],
);
}
}),
)
],
),
);
}
}
The problem: The z-index is not correct on my element. My header is above. How could I adjust the z-index? I know this from CSS. Is there a way to to this with flutter?
One way of achieving overlapping widgets is by using Stack widget. You can check the docs for more details.
try this package https://pub.dev/packages/indexed
https://raw.githubusercontent.com/physia/kflutter/main/indexed/doc/assets/demo.gif
This package allows you to order items inside stack using index like z-index in css
this is example how it works
Indexer(
children: [
Indexed(
index: 100,
child: Positioned(
//...
)
),
Indexed(
index: 1000,
child: Positioned(
//...
)
),
Indexed(
index: 3,
child: Positioned(
//...
)
),
],
);
if you are using bloc of some complex widget you can extands or implement the IndexedInterface class and override index getter:
class IndexedDemo extends IndexedInterface {
int index = 5;
}
or implements
class IndexedDemo extends AnimatedWidget implements IndexedInterface {
int index = 1000;
//...
//...
}
then use it just like Indexed class widget:
Indexer(
children: [
IndexedDemo(
index: 100,
child: Positioned(
//...
)
),
IndexedFoo(
index: 1000,
child: Positioned(
//...
)
),
],
);
Online demo
Video demo