Related
I made slide learning app but slide was looping playing continuously
I need to stop auto play it reaches the end.
I'm using card_swiper: ^1.0.2
I update my full code is here
-I'm also using a Assets audio player
I try loop: false it won't work
Thanks in advance
I don't know how to use it
import 'package:assets_audio_player/assets_audio_player.dart';
import 'package:card_swiper/card_swiper.dart';
import 'package:flutter/material.dart';
class Chemistry extends StatefulWidget {
#override
_ChemistryState createState() => _ChemistryState();
}
class _ChemistryState extends State<Chemistry> {
List images = [
'assets/images/che/s.jpg',
'assets/images/che/t.jpg',
'assets/images/che/u.jpg',
'assets/images/che/v.jpg',
'assets/images/che/w.jpg',
'assets/images/che/x.jpg',
'assets/images/che/y.jpg',
'assets/images/che/z.jpg',
];
#override
Widget build(BuildContext context) {
return Scaffold(
body:Swiper(
itemCount: images.length,
loop: false,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(27.0),
child: Image.asset(
images[index],
),
);
},
indicatorLayout: PageIndicatorLayout.COLOR,
onIndexChanged: (index) {
playaudio(index);
},
autoplayDelay: 4000,
autoplay: true,
pagination: FractionPaginationBuilder(
color: Colors.red, activeColor: Colors.green, fontSize: 10),
// control: SwiperControl(),
),
),
);
}
}
void playaudio(index) async {
AssetsAudioPlayer.newPlayer().open(
Audio('assets/audio/Chemistry/a$index.mp3'),
);
}
You can set loop: false - to disable continuous loop mode.
If you want autoplay, then set autoplay: true
According to the documentation https://pub.dev/packages/card_swiper you can set loop attribute to false
Swiper(
itemBuilder: (BuildContext context,int index){
return Image.network("https://via.placeholder.com/350x150",fit: BoxFit.fill,);
},
itemCount: 3,
loop:false,
pagination: SwiperPagination(),
),
I am developing an app that has a bottomnavitaionbar with five pages. I use getx. In first page, i am listing data. My problem is that, when i changed data(first page in bottomnavigationbar) manually from database and thn i pass over pages, came back to first page i could not see changes.
Controller;
class ExploreController extends GetxController {
var isLoading = true.obs;
var articleList = List<ExploreModel>().obs;
#override
void onInit() {
fetchArticles();
super.onInit();
}
void fetchArticles() async {
try {
isLoading(true);
var articles = await ApiService.fetchArticles();
if (articles != null) {
//articleList.clear();
articleList.assignAll(articles);
}
} finally {
isLoading(false);
}
update();
}
}
and my UI;
body: SafeArea(
child: Column(
children: <Widget>[
Header(),
Expanded(
child: GetX<ExploreController>(builder: (exploreController) {
if (exploreController.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),
);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {
thanks to #Baker for the right answer. However, if you have a list and in viewModel and want to update that list, just use the list.refresh() when the list updated
RxList<Models> myList = <Models>[].obs;
when add or insert data act like this:
myList.add(newItem);
myList.refresh();
GetX doesn't know / can't see when database data has changed / been updated.
You need to tell GetX to rebuild when appropriate.
If you use GetX observables with GetX or Obx widgets, then you just assign a new value to your observable field. Rebuilds will happen when the obs value changes.
If you use GetX with GetBuilder<MyController>, then you need to call update() method inside MyController, to rebuild GetBuilder<MyController> widgets.
The solution below uses a GetX Controller (i.e. TabX) to:
hold application state:
list of all tabs (tabPages)
which Tab is active (selectedIndex)
expose a method to change the active/visible tab (onItemTapped())
OnItemTapped()
This method is inside TabX, the GetXController.
When called, it will:
set which tab is visible
save the viewed tab to the database (FakeDB)
rebuild any GetBuilder widgets using update()
void onItemTapped(int index) {
selectedIndex = index;
db.insertViewedPage(index); // simulate database update while tabs change
update(); // ← rebuilds any GetBuilder<TabX> widget
}
Complete Example
Copy/paste this entire code into a dart page in your app to see a working BottomNavigationBar page.
This tabbed / BottomNavigationBar example is taken from
https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html
but edited to use GetX.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyTabHomePage(),
);
}
}
class FakeDB {
List<int> viewedPages = [0];
void insertViewedPage(int page) {
viewedPages.add(page);
}
}
/// BottomNavigationBar page converted to GetX. Original StatefulWidget version:
/// https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html
class TabX extends GetxController {
TabX({this.db});
final FakeDB db;
int selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
List<Widget> tabPages;
#override
void onInit() {
super.onInit();
tabPages = <Widget>[
ListViewTab(db),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
}
/// INTERESTING PART HERE ↓ ************************************
void onItemTapped(int index) {
selectedIndex = index;
db.insertViewedPage(index); // simulate database update while tabs change
update(); // ← rebuilds any GetBuilder<TabX> widget
// ↑ update() is like setState() to anything inside a GetBuilder using *this*
// controller, i.e. GetBuilder<TabX>
// Other GetX controllers are not affected. e.g. GetBuilder<BlahX>, not affected
// by this update()
// Use async/await above if data writes are slow & must complete before updating widget.
// This example does not.
}
}
/// REBUILT when Tab Page changes, rebuilt by GetBuilder in MyTabHomePage
class ListViewTab extends StatelessWidget {
final FakeDB db;
ListViewTab(this.db);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: db.viewedPages.length,
itemBuilder: (context, index) =>
ListTile(
title: Text('Page Viewed: ${db.viewedPages[index]}'),
),
);
}
}
class MyTabHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
Get.put(TabX(db: FakeDB()));
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
/// ↓ Tab Page currently visible - rebuilt by GetBuilder when
/// ↓ TabX.onItemTapped() called
child: GetBuilder<TabX>(
builder: (tx) => tx.tabPages.elementAt(tx.selectedIndex)
),
),
/// ↓ BottomNavBar's highlighted/active item, rebuilt by GetBuilder when
/// ↓ TabX.onItemTapped() called
bottomNavigationBar: GetBuilder<TabX>(
builder: (tx) => BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: tx.selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: tx.onItemTapped,
),
),
);
}
}
You don't need GetBuilder here, as its not meant for observable variables. Nor do you need to call update() in the fetchArticles function as that's only for use with GetBuilder and non observable variables.
So you had 2 widgets meant to update UI (GetBuilder and Obx) both following the same controller and all you need is just the OBX. So Rahuls answer works, or you can leave the Obx in place, get rid of of the GetBuilder and declare and initialize a controller in the beginning of your build method.
final exploreController = Get.put(ExploreController());
Then use that initialized controller in your OBX widget as the child of your Expanded.
Obx(() => exploreController.isLoading.value
? Center(
child:
SpinKitChasingDots(color: Colors.deepPurple[600], size: 40),
)
: ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {},
),
)
GetX< ExploreController >(builder: (controller) {
if (controller.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: controller.articleList.length,
separatorBuilder: (BuildContext context, int index) {});
});
If you change the value in the database 'manually', you need a STREAM to listen to the change on the database.
You can't do:
var articles = await ApiService.fetchArticles();
You need to do something like this:
var articles = await ApiService.listenToArticlesSnapshot();
The way you explained is like if you need the data to refresh after navigating to another page and clicking on a button, then navigating to first page (GetBuilder) OR automatically adds data from the within the first page (Obx). But your case is simple, just retrieve the articles SNAPSHOT, then in the controller onInit, subscribe to the snapshot with the bindStream method, and eventually use the function ever() to react to any change in the observable articleList.
Something like this:
create
final exploreController = Get.put(ExploreController());
Add
init: ExploreController();
body: SafeArea(
child: Column(
children: <Widget>[
Header(),
Expanded(
child: GetX<ExploreController>(builder: (exploreController) {
*** here ***
init: ExploreController();
if (exploreController.isLoading.value) {
return Center(
child: SpinKitChasingDots(
color: Colors.deepPurple[600], size: 40),
);
}
return ListView.separated(
padding: EdgeInsets.all(12),
itemCount: exploreController.articleList.length,
separatorBuilder: (BuildContext context, int index) {
using GetxBuilder approch on ui side and where you want update simple called built in function update();
The simplest way I could.
In the controller create an obs (var indexClick = 1.obs;)
On each Tile test the selected==index...;
On the click of each item change the indexClick sequentially
return Obx(() {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
ListTile(
leading: const Icon(Icons.dns),
title: const Text('Menu1'),
selected: controller.indexClick.value==1?true:false,
onTap: () {
controller.indexClick.value=1;
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.search),
title: const Text('Menu2'),
selected: controller.indexClick.value==2?true:false,
onTap: () {
controller.indexClick.value=2;
Navigator.pop(context);
},
),
I show my list of answers via ListView.builder and check value on checkbox work ok, but when I scroll down and turn back checked value is lost. Other way when lost focus in checked answer automatic checkbox lost checked value.
Below is my code. I would be grateful if someone could help me.
class AnswerItem extends StatefulWidget {
#override
_AnswerItemState createState() => _AnswerItemState();
}
class _AnswerItemState extends State<AnswerItem> {
List<bool> _data = [false, false, false, false];
void _onChange(bool value, int index) {
setState(() {
_data[index] = value;
});
}
Widget build(BuildContext context) {
final questionItems = Provider.of<Item>(context);
List<Answer> listOfAnswers = questionItems.answers.toList();
return SingleChildScrollView(
child:
ListView.builder(
shrinkWrap: true,
itemCount: listOfAnswers.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Card(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: CheckboxListTile(
value: _data[index],
title: Text(listOfAnswers[index].title),
onChanged: (val) {
_onChange(val, index);
},
),
),
),
);
},
),
);
}
}
Somewhere, you're confusing "model" with "view", and storing state in the view. This necessarily means when the view is refreshed or updated, you will lose state.
Specifically, your model here appears to be listOfAnswers, which being a local variable inside a build() method, will possibly be rebuilt on every refresh (possibly 120 fps!). You need to put your model outside any build method.
Currently using the flutter video_player plugin stream video from the given link. Issue is that I had to hide the normal video interactive interface so that user can't skip the video. Now most of the work is done, just need to know how to display duration and current position of the video been played.
videoController.value.duration.inSeconds gives me the duration part, and videoController.value.position gives the position. But how to keep updating the results for theposition` section?
void checkTimer(){
if(playerController.value.position == playerController.value.duration){
setState(() {
Duration duration = Duration(milliseconds: playerController?.value?.position?.inMilliseconds?.round());
nowTime = [duration.inHours, duration.inMinutes, duration.inSeconds]
.map((seg) => seg.remainder(60).toString().padLeft(2, '0'))
.join(':');
});
}
above code was created to update the time as needed. but now the issue is how to update time. should I use setState() or something else, because the above code is not working for me.
Video is not loaded where then screen is loaded. It's loaded when then users click the play button. so till that time, we don't even have a duration value as data is still on the wayt.
How about using ValueListenableBuilder ?
It will listen to the controller's value and update it every time the value changes.
here's the sample :
ValueListenableBuilder(
valueListenable: playerController,
builder: (context, VideoPlayerValue value, child) {
//Do Something with the value.
return Text(value.position.toString());
},
);
use the built-in widget from the video player plugin.
[See more on their example on github][https://github.com/999eagle/plugins/blob/master/packages/video_player/example/lib/main.dart]
VideoProgressIndicator(
_videoController,//controller
allowScrubbing: true,
colors: VideoProgressColors(
playedColor: primary,
bufferedColor: Colors.red,
backgroundColor: black,
),
)
[1]: https://github.com/999eagle/plugins/blob/master/packages/video_player/example/lib/main.dart
Try this :
Create a new Stateful Widget to display the counter for current position,
Pass videoPlayerController as a parameter in the widget ,
Listen to the videoPlayerController in initState and add setSate to the listened value
Here's the code,
const _currentVideoPositionWidth = 38.0;
const _minTwoDigitValue = 10;
class _CurrentVideoPosition extends StatefulWidget {
const _CurrentVideoPosition({
Key? key,
required this.videoPlayerController,
}) : super(key: key);
final VideoPlayerController videoPlayerController;
#override
_CurrentVideoPositionState createState() => _CurrentVideoPositionState();
}
class _CurrentVideoPositionState extends State<_CurrentVideoPosition> {
int currentDurationInSecond = 0;
#override
void initState() {
widget.videoPlayerController.addListener(
() => setState(() => currentDurationInSecond = widget.videoPlayerController.value.position.inSeconds),
);
super.initState();
}
#override
Widget build(BuildContext context) => Container(
width: _currentVideoPositionWidth,
alignment: Alignment.centerRight,
child: Text(
_formatCurrentPosition(),
style: Theme.of(context).textTheme.bodyText1?.copyWith(
color: Colors.white,
),
maxLines: 1,
),
);
String _formatCurrentPosition() =>
currentDurationInSecond < _minTwoDigitValue ? "0 : 0$currentDurationInSecond" : "0 : $currentDurationInSecond";
}
late VideoPlayerController _phenikaaVideoPlayerController;
late Future<void> _initializeVideoPlayerFuture;
#override
void initState() {
super.initState();
_phenikaaVideoPlayerController = VideoPlayerController.network(
"https://assets-phenikaa-website.s3.ap-southeast-
1.amazonaws.com/media/assets/mo-hinh-3-nha.mp4",
);
// Initialize the controller and store the Future for later use.
_initializeVideoPlayerFuture =
_phenikaaVideoPlayerController.initialize();
}
#override
void dispose() {
_phenikaaVideoPlayerController.dispose();
super.dispose();
}
FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (_, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Column(
children: [
// If the VideoPlayerController has finished initialization, use
// the data it provides to limit the aspect ratio of the video.
AspectRatio(
aspectRatio:
_phenikaaVideoPlayerController.value.aspectRatio,
// Use the VideoPlayer widget to display the video.
child: VideoPlayer(_phenikaaVideoPlayerController),
),
// show the video progress & scrubbing by touch event on it
VideoProgressIndicator(
_phenikaaVideoPlayerController,
allowScrubbing: true,
padding: EdgeInsets.zero,
colors: VideoProgressColors(
backgroundColor: Color(0xFF243771),
playedColor: R.colors.redFF0000,
bufferedColor: R.colors.grayF5F6F8,
),
),
SizedBox(height: R.dimens.smallSpacing),
Row(
children: [
SizedBox(width: R.dimens.smallSpacing2),
InkWell(
onTap: () {
if (_phenikaaVideoPlayerController.value.isPlaying) {
_phenikaaVideoPlayerController.pause();
} else {
_phenikaaVideoPlayerController.play();
}
},
child: ValueListenableBuilder<VideoPlayerValue>(
valueListenable: _phenikaaVideoPlayerController,
builder: (_, _videoPlayerValue, __) {
return Icon(
_videoPlayerValue.isPlaying
? Icons.pause_circle_outline_rounded
: Icons.play_circle_outline_rounded,
);
},
),
),
SizedBox(width: R.dimens.smallSpacing2),
InkWell(
onTap: () {
_phenikaaVideoPlayerController
.seekTo(Duration(seconds: 0));
_phenikaaVideoPlayerController.pause();
},
child: Icon(Icons.stop_circle_outlined),
),
SizedBox(width: R.dimens.smallSpacing2),
// render duration video with current position / total video duration
ValueListenableBuilder<VideoPlayerValue>(
valueListenable: _phenikaaVideoPlayerController,
builder: (_, _videoPlayerValue, __) {
return Text(
"00:${_videoPlayerValue.position.inSeconds.toString().padLeft(2, '0')}",
style: R.styles.titleTextStyleW500S16,
);
},
),
Text(
" / 00:${_phenikaaVideoPlayerController.value.duration.inSeconds.toString()}",
style: R.styles.titleTextStyleW500S16,
),
Spacer(),
//render Volume button
InkWell(
onTap: () {
if (_phenikaaVideoPlayerController.value.volume ==
0.0) {
_phenikaaVideoPlayerController.setVolume(1.0);
} else
_phenikaaVideoPlayerController.setVolume(0.0);
},
child: ValueListenableBuilder<VideoPlayerValue>(
valueListenable: _phenikaaVideoPlayerController,
builder: (_, _videoPlayerValue, __) {
return Icon(
_videoPlayerValue.volume == 0.0
? Icons.volume_off_outlined
: Icons.volume_up_outlined,
);
},
),
),
SizedBox(width: R.dimens.smallSpacing2),
],
),
],
);
} else {
// If the VideoPlayerController is still initializing, show a
// loading spinner.
return Container(
alignment: Alignment.center,
padding: EdgeInsets.only(top: R.dimens.mediumSpacing1),
child: CircularProgressIndicator(
color: Color(0xFF243771),
),
);
}
},
),
Follow my widget tree with the image demo below
I created a list to try an display the issue I am encountering with flutter.
Every time you click on a list item button, the button below it is removed. As you can see from the gif below when you click on the button it creates a second copy of the bottom element.
Paused mid animation it looks like this:
To create the AnimtedList I started with giving it a global key:
final GlobalKey<AnimatedListState> _ListKey = GlobalKey();
Then I create a list of colors like this:
List<Color> listColors = [Colors.orange, Colors.green, Colors.red, Colors.blue, Colors.yellowAccent, Colors.brown,];
Then I have an AnimatedList like this, which has initial size of the listColors length and child of _buildListItem:
AnimatedList(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
key: _ListKey,
initialItemCount: listColors.length,
itemBuilder: (context, index, animation) {
return _buildListItem(index, animation);
},
),
This is the build list item method, a SizeTransition that has a child of the List_Element:
SizeTransition _buildListItem(int index, Animation<double> animation,) {
return SizeTransition(
sizeFactor: animation,
child: List_Element(index),
);
}
This is the List_Element,the rows of the list with a simple button with color set by the index of the list of colors. In the onPressed method I call the removeFromListFunction to remove the row below.
class List_Element extends StatefulWidget {
int listIndex;
List_Element(int listIndex) {
this.listIndex = listIndex;
}
#override
_List_ElementState createState() => _List_ElementState();
}
class _List_ElementState extends State<List_Element> {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(4),
child: Container(
width: double.infinity,
height: 50,
child: RaisedButton(
color: listColors[widget.listIndex],
elevation: 2,
child: Center(child: Text("List item " + widget.listIndex.toString(), style: TextStyle(fontWeight: FontWeight.bold),),),
onPressed: (){
_removeFromList(widget.listIndex);
},
),
),
);
}
}
This is the removeFromList function:
void _removeFromList(int index) {
listColors.remove(int);
_ListKey.currentState.removeItem(index+1,
(BuildContext context, Animation<double> animation) {
return _buildListItem(index, animation);
});
}
I am not sure if it a problem with animated list or more likely my implementation of it.
Thanks for your help
void _removeFromList(int index) {
listColors.remove(int);
_ListKey.currentState.removeItem(index+1,
(BuildContext context, Animation<double> animation) {
//return _buildListItem(index, animation);
return _buildListItem(index + 1, animation);
});
}
If I'm not mistaken, the reason why this is happening is that you are passing the index of the "currently clicked" button when you are rebuilding the "removed" button. Thus its displaying the clicked button again.