How to Integrate D-Pad Focus Navigation? - android

I'm currently trying to make a Netflix-style UI in Flutter on my Android TV. I've been struggling with integrating focus navigation for the past few days and figured I'd ask here as there doesn't really seem to be any in-depth tutorials.
Right now I'd like to be able to navigate my ListView Builder using d-pad controls and add a special state to the widget to signify that it's currently selected (enlarged or with a border).
Here's my current code:
class Home extends StatefulWidget {
Home({Key key}) : super(key: key);
#override
HomeState createState() => new HomeState();
}
class HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(left: 8.0, right: 8.0),
child: FutureBuilder(
// an asset :bundle is just the resources used by a given application
future: DefaultAssetBundle.of(context)
.loadString('assets/json/featured.json'),
builder: (context, snapshot) {
// Loading indicator
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text(snapshot.error); // or whatever
}
var mediaData = json.decode(snapshot.data.toString());
List<Session> mediaDataSession =
(mediaData as List<dynamic>).cast<Session>();
// Pass our array data into the Session class' fromJson method and allow it to be iterable
var decodedData =
mediaData.map((data) => Session.fromJson(data)).toList();
BoxDecoration myBoxDecoration() {
return BoxDecoration(
border: Border.all(),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 30.0),
child: Align(
alignment: Alignment.bottomLeft,
child: Text('Featured',
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'HelveticaNeue',
fontSize: 24.0)),
),
),
Expanded(
// Focus was here before
child: ListView.builder(
itemCount: mediaData.length,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
return Align(
alignment: Alignment.topLeft,
child: Container(
margin:
EdgeInsets.only(right: 8.0, top: 4.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment
.start, // Workaround for aligning text
children: [
Container(
// Find a way to render based on focus being true
decoration: myBoxDecoration(),
child: InkWell(
// onTap: () => {},
borderRadius: BorderRadius.circular(4.0),
focusColor: Colors.black,
child: SizedBox(
height: 150.0,
child: ClipRRect(
child: Image.network(
mediaData[index]['image'])),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 6.0),
child: Text(mediaData[index]['name'],
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 17.0,
fontFamily: "HelveticaNeue",
fontWeight: FontWeight.normal)),
)
],
),
),
);
},
),
),
],
);
}),
);
}
}
For reference, I'd like to get behaviour like this video here: https://www.youtube.com/watch?v=l37VYXhRhPQ
Any help would be appreciated!

Here is the source code of the app in the video:
https://gitlab.com/ad-on-is/chillyflix/

Related

How to have a ListView without scrolling the parent column view in flutter?

Please look at this image Home Page
Now when I scroll the ListView it becomes like this -
Home Page
Now I know the reason why this is happening, it is because I used ListView as a parent to this entire view and added ListView.builder() and other widgets as its child.
What I want is to scroll the ListView.builder() without scrolling the entire page.
For this I first tried to use Column as parent but that ended up giving the overflow pixels error.
And then I set the physics: const NeverScrollableScrollPhysics() inside the parent ListView but after that it made my ListView.builder() to show all of its list items.
Here is my Code for Home Screen
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: ListView(
padding: const EdgeInsets.only(top: 45, bottom: 24),
children: [
header(),
const SizedBox(height: 36),
const BalanceCard(),
const SizedBox(height: 36),
Recent()
],
),
),
Align(
alignment: Alignment.bottomCenter,
child: bottomNavigationBar(),
),
],
),
); }
Recent List Code
class RecentItems extends StatefulWidget {
final List<Transaction> transactions;
RecentItems({required this.transactions});
#override
State<RecentItems> createState() => _RecentItemsState();
}
class _RecentItemsState extends State<RecentItems> {
#override
Widget build(BuildContext context) {
return SizedBox(
height: 450,
child: Expanded(child: ListView.builder(
itemBuilder: (context, index) {
final item = widget.transactions[index].toString();
return Dismissible(
direction: DismissDirection.endToStart,
key: UniqueKey(),
onDismissed: (direction) {
setState(() {
widget.transactions.removeAt(index);
});
// Then show a snackbar.
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text('Transaction Deleted')));
},
background: Container(
color: Colors.red,
alignment: AlignmentDirectional.centerEnd,
child: const Padding(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 15.0, 0.0),
child: Icon(
EvaIcons.trash2,
color: Colors.white,
),
),
),
child: Card(
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: ListTile(
leading: CircleAvatar(
radius: 30,
foregroundImage: widget.transactions[index].Image,
backgroundColor: primaryColor,
),
title: Text(
widget.transactions[index].title,
style: const TextStyle(color: secondaryColor),
),
subtitle: Text(
DateFormat.yMMMd().format(widget.transactions[index].date),
),
trailing: Text(
'\$${widget.transactions[index].amount}',
style: const TextStyle(color: secondaryColor),
),
),
),
);
},
itemCount: widget.transactions.length,
),)
);
}
}
Recent Widget -
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Recent Transactions',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: secondaryColor),
),
const SizedBox(height: 5),
RecentItems(transactions: _userTransactions),
],
),
)
The entire screen scrolls because Recent() is included in the same ListView as header() and balanceCard().
Try something like this:
Scaffold(
body: Column(
children: [
Expanded(
child: ListView(
padding: const EdgeInsets.only(top: 45, bottom: 24),
children: [
header(),
const SizedBox(height: 36),
const BalanceCard(),
const SizedBox(height: 36),
],
),
),
// Recent items removed from ListView
Recent(),
Align(
alignment: Alignment.bottomCenter,
child: bottomNavigationBar(),
),
],
),
)
I hope this helps.
By simplifying your code, this is an example of a layout where you have a single Column in the Scaffold. The Column contains some sized, unsized and aligned children.
One child, Recent is a ListView, without explicit height, but wrapped into an Expanded widget. This way it will occupy all the remaining area left by the other children, and it will be scrollable.
(You will run into trouble with this if the children without Recent occupy all the available area.)
Please have a look at this code, you can copy-paste it into a DartPad fiddle:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) => Scaffold(
body: Column(
children: [
const Text('header()'),
const SizedBox(height: 36),
const Text('BalanceCard()'),
const SizedBox(height: 36),
Expanded(child: Recent()),
const Align(
alignment: Alignment.bottomCenter,
child: Text('bottomNavigationBar()'),
),
],
),
);
}
class Recent extends StatelessWidget {
#override
Widget build(BuildContext context) => ListView.builder(
itemCount: 100, itemBuilder: (context, index) => Text('Item $index'));
}

Flutter How to make a pagination from streambuilder with firestore (lazy loading)

Hello please I am new in flutter mobile devellopement. I would like to do a pagination (Lazy loading from streambuilder with firestore). Indeed when I do a stream all the documents load and it takes a long time and also my application sometimes bugs (maybe because I loaded a lot of data in memory). I would like to simplify things by using a pagination but I don't really know how to do it. or you can load 10 documents per call. please help me find a solution to avoid bugging my application and load less document per call. here is the full code of the feed part
class FeedJob extends StatefulWidget {
FeedJob({Key? key}) : super(key: key);
#override
_FeedJobState createState() => _FeedJobState();
}
class _FeedJobState extends State<FeedJob> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("job_feed")
.orderBy("time", descending: true)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Column(
children: [
Expanded(
flex: 0,
child: Column(children: [
TiTle(title: "Feeds"),
])),
Expanded(
child: ListView(
children: [
ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: snapshot.data!.docs.map((e) {
return Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
child: Column(
children: [
ListTile(
leading: Container(
width: 40,
height: 40,
alignment: Alignment.topCenter,
decoration: BoxDecoration(
image: DecorationImage(
alignment:
Alignment.topCenter,
fit: BoxFit.cover,
image:
CachedNetworkImageProvider(
e.get(
'photo'))),
color: Colors.blue
.withOpacity(.2),
borderRadius:
BorderRadius.all(
Radius.circular(
20))),
),
trailing: Column(
children: [
Icon(Icons.comment_rounded,
size: 15,
color: Colors.grey),
Text("comment".tr,
style: TextStyle(
fontSize: 8,
color: Colors.grey))
],
),
title: Text(e.get('name'),
style: TextStyle(
color: Colors.black,
fontSize: 10,
fontWeight:
FontWeight.bold)),
subtitle:
Text(e.get('time').toString(),
style: TextStyle(
fontSize: 8,
color: Colors.grey,
)),
),
Padding(
padding: const EdgeInsets.only(
left: 5.0,
right: 8,
bottom: 15),
child: Text(
e.get('description'),
textAlign: TextAlign.justify,
),
)
],
),
),
)
],
);
}).toList()),
],
),
),
],
);
}
}));
}
}
You can use startAfterDocument to tell Firestore what was the last document you fetched (assuming you keep a reference to it each time).
// Prepare the query.
final List<JobModel> fetchedData = [];
final int firstBatchSize = 6;
final int nextBatchSize = 12;
DocumentSnapshot<JobModel>? lastDocument;
final Query<DestinationModel> query = FirebaseFirestore.instance
.collection('jobs')
.orderBy('time', descending: true)
.withConverter<JobModel>(
fromFirestore: (snapshot, _) => JobModel.fromFirestore(snapshot),
toFirestore: (JobModel job, _) => job.toFirestore(),
);
// Set the starting point of the query.
if (lastDocument != null) query.startAfterDocument(lastDocument);
// Set the limit for the query.
query.limit(fetchedData.isEmpty ? firstBatchSize : nextBatchSize);
// Run the query.
final QuerySnapshot<JobModel> results = await query.get();
// Do something with the results; Store the last document fetched.

Flutter DragTarget onAccept is not being called

I am developing a feature where the user enters a sentence, in the next screen the words of that sentence get shuffled randomly, then the user has to drag the words to a drag target to form the original sentence.
You can get an idea from the screenshots below.
First screen
Second screen
Now the problem I am having is, when dragging the words to the target I can see the DragTarget is calling onWillAccept as I added a print() statement there, if it is doing so then it should call onAccept eventually but it is not doing so. This is why my codes that deal with Bloc are not getting called and the words are not showing up in the target spot.
Code
class SentenceMakeScreen extends StatefulWidget {
String inputSentence;
SentenceMakeScreen(this.inputSentence);
#override
State<SentenceMakeScreen> createState() => _SentenceMakeScreenState();
}
class _SentenceMakeScreenState extends State<SentenceMakeScreen> {
List<String> sentence = [];
List<Widget> wordWidgets = [];
bool isDragSuccessful = false;
final ButtonStyle _buttonStyle = ElevatedButton.styleFrom(
textStyle: TextStyle(fontSize: 20)
);
_getTextWidgets(List<String> sentence) {
for(var i = 0; i < sentence.length; i++){
wordWidgets.add(
Draggable<WordWidget>(
data: WordWidget(sentence[i]),
child: WordWidget(sentence[i]),
feedback: WordWidget(sentence[i]),
childWhenDragging: Container(),
)
);
}
}
_randomlyOrganizeSentence(String inputString) {
sentence = inputString.split(new RegExp(r" "));
sentence.shuffle();
print(sentence);
}
#override
void initState() {
// TODO: implement initState
_randomlyOrganizeSentence(widget.inputSentence);
_getTextWidgets(sentence);
super.initState();
}
#override
Widget build(BuildContext context) {
final _dragDropBloc = DragDropBloc();
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
),
body: Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
DragTarget<WordWidget>(
builder: (context, data, rejectedData) {
return Center(
child: this.isDragSuccessful
?
Container(
width: double.maxFinite,
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(width: 1.0, color: Colors.black),
),
),
child: StreamBuilder<List<WordWidget>>(
stream: _dragDropBloc.widgetStream,
initialData: [],
builder: (BuildContext context, AsyncSnapshot<List<WordWidget>> snapshot) {
print("Here ${snapshot.data}");
return Wrap(
direction: Axis.horizontal,
children: [
//correctly ordered words
],
);
},
),
)
:
Container(
width: double.maxFinite,
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(width: 1.0, color: Colors.black),
),
),
child: Text("Drag here")
),
);
},
onWillAccept: (data) {
print("true");
return true;
},
onAccept: (data) {
print(data.toString());
_dragDropBloc.dragDropEventSink.add(
DropEvent(WordWidget(data.toString()))
);
setState(() {
this.isDragSuccessful = true;
//draggedWords.add(data.toString());
});
},
),
Wrap(
direction: Axis.horizontal,
children: wordWidgets
),
Container(
child: ElevatedButton(
style: _buttonStyle,
onPressed: () {
},
child: Text("Check"),
),
),
],
),
),
);
}
}
WordWidget
import 'package:flutter/material.dart';
class WordWidget extends StatelessWidget {
final String word;
const WordWidget(this.word);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.red[900],
border: Border.all(
width: 4,
color: Colors.black
),
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: EdgeInsets.all(5),
child: Text(
word,
style: TextStyle(
color: Colors.white
),
)
),
);
}
}
I tried adding the type of data I am passing from Draggable to DragTarget, this is what was advised here. It did not work.
I was also getting the same error earlier today. I then upgraded my flutter to the latest version and wrote the DragTarget code again from scratch. I don't know what worked for me but you can try doing the same.

How to call a method from another class in flutter

I'm Beginner to coding. I have created a Image picker in flutter, I want to use the image picker in many different pages, so I have created a separate class, but when I call the method in other pages, it just open the gallery but,it is not picking the image from gallery and displaying the picked image.There is no any error.
Kindly, help to solve the issue.
Thanks in advance
My Code:
main.dart:
import 'package:flutter/material.dart';
import 'package:project1test/healthscreen_expat.dart';
import 'package:project1test/forms/parkings.dart';
class accomodationform extends StatefulWidget {
String text;
accomodationform(String name) {
text = name;
}
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return MyAppState(text);
}
}
class MyAppState extends State<accomodationform> {
Mod1 mod11 = new Mod1();
String labels;
MyAppState([String label]) {
labels = label;
}
Image1 im = Image1();
final scaffoldkey = new GlobalKey<ScaffoldState>();
final formkey = new GlobalKey<FormState>();
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: new Padding(
padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 20),
child: new Form(
key: formkey,
child: ListView(children: <Widget>[
mod11.user(),
]))),
);
}
}
imagepick.dart
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class Mod1 {
var images1accom;
user() {
List<dynamic> img = List();
return Container(
margin: EdgeInsets.only(top: 20, right: 20, left: 20),
padding: EdgeInsets.only(top: 20.0),
width: double.infinity,
height: 150.0,
color: Colors.white70,
child: Center(
child: Row(
//mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlineButton(
onPressed: () async {
images1accom =
await ImagePicker.pickImage(source: ImageSource.gallery);
img.add(images1accom);
},
child: Row(children: <Widget>[
Icon(Icons.camera_alt),
Text(
"Choose File",
style: TextStyle(fontSize: 12.0),
textAlign: TextAlign.end,
)
]),
borderSide: BorderSide(color: Colors.pink),
textColor: Colors.pinkAccent,
padding: EdgeInsets.all(10.0),
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(10.0),
)),
Expanded(
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: img.length,
itemBuilder: (BuildContext c, int position) {
return (Image.file(
img[position],
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
));
},
),
),
],
),
),
);
}
}
Well, I think maybe it would be good for you to study object-oriented programming, dart, and how Flutter works.
Initially, I need to tell you that you simply cannot do what you are trying to do, insert widgets within classes, with separate functions, and try to instantiate it within a Stateful.
Widgets must not be instantiated, and if you want to componentize something, you must do it using a stateful or stateless class, not an ordinary class.
Your Mod class should look like this:
class ChoosePic extends StatefulWidget {
ChoosePic({Key key}) : super(key: key);
#override
_ChoosePicState createState() => _ChoosePicState();
}
class _ChoosePicState extends State<ChoosePic> {
List<dynamic> img = List();
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 20, right: 20, left: 20),
padding: EdgeInsets.only(top: 20.0),
width: double.infinity,
height: 150.0,
color: Colors.white70,
child: Center(
child: Row(
//mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlineButton(
onPressed: () async {
File images1accom =
await ImagePicker.pickImage(source: ImageSource.gallery);
img.add(images1accom);
setState(() {});
},
child: Row(children: <Widget>[
Icon(Icons.camera_alt),
Text(
"Choose File",
style: TextStyle(fontSize: 12.0),
textAlign: TextAlign.end,
)
]),
borderSide: BorderSide(color: Colors.pink),
textColor: Colors.pinkAccent,
padding: EdgeInsets.all(10.0),
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(10.0),
)),
Expanded(
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: img.length,
itemBuilder: (BuildContext c, int position) {
return (Image.file(
img[position],
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
));
},
),
),
],
),
),
);
}
}
And you can to use it with
child: ChoosePic()
I have no idea why you are using a listview in your main class, but if it is really necessary, you would do this:
ListView(children: <Widget>[
ChoosePic(),
])
If you want the value of img, you will need a state manager for this:
Using Get (add this package to pubspec):
https://pub.dev/packages/get
Create class with shared state:
class Controller extends GetController {
static Controller get to => Get.find();
List<dynamic> img = List();
takeImage() {
File images1accom =
await ImagePicker.pickImage(source: ImageSource.gallery);
img.add(images1accom);
update(this);
}
}
// use it:
class ChoosePic extends StatefulWidget {
ChoosePic({Key key}) : super(key: key);
#override
_ChoosePicState createState() => _ChoosePicState();
}
class _ChoosePicState extends State<ChoosePic> {
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 20, right: 20, left: 20),
padding: EdgeInsets.only(top: 20.0),
width: double.infinity,
height: 150.0,
color: Colors.white70,
child: Center(
child: Row(
//mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlineButton(
onPressed: () async {
Controller.to.takeImage();
},
child: Row(children: <Widget>[
Icon(Icons.camera_alt),
Text(
"Choose File",
style: TextStyle(fontSize: 12.0),
textAlign: TextAlign.end,
)
]),
borderSide: BorderSide(color: Colors.pink),
textColor: Colors.pinkAccent,
padding: EdgeInsets.all(10.0),
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(10.0),
)),
Expanded(
child: GetBuilder<Controller>(
init: Controller(),
builder: (controller) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: controller.img.length,
itemBuilder: (BuildContext c, int position) {
return (Image.file(
controller.img[position],
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
));
},
);
}
),
),
],
),
),
);
}
}
Now you can get the image list from anywhere in your code with:
on widget three controller.img;
GetBuilder<Controller>(
init: Controller(),
builder: (controller) {
Example:
GetBuilder<Controller>(
init: Controller(),
builder: (controller) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: controller.img.length,
itemBuilder: (BuildContext c, int position) {
return (Image.file(
controller.img[position],
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
));
},
);
And take it out of the widget tree with:
Controller.to.img
Note: init: Controller() can only be used once, if you need GetBuilder elsewhere, don't use it. Use, for example:
GetBuilder<Controller>(
builder: (controller) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: controller.img.length,
itemBuilder: (BuildContext c, int position) {
return (Image.file(
controller.img[position],
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
));
},
);
Well, I shouldn't answer that, as it qualifies as a general question, but since you are a beginner, I answered to help you, in detail. I hope you understand the basics soon, and become a great developer someday.
Welcome to Flutter!
You need to create a separate widget for Mod1 class.
MyAppState
Widget build(BuildContext context) {
return Scaffold(
body: new Padding(
padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 20),
child: new Form(key: formkey, child: Mod1())),
);
}
Mod1 widget
class Mod1 extends StatefulWidget {
#override
State<StatefulWidget> createState() => Mod1State();
}
class Mod1State extends State<Mod1> {
var images1accom;
List<dynamic> img = List();
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 20, right: 20, left: 20),
padding: EdgeInsets.only(top: 20.0),
width: double.infinity,
height: 150.0,
color: Colors.white70,
child: Center(
child: Row(
//mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlineButton(
onPressed: () async {
images1accom =
await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
img.add(images1accom);
});
},
child: Row(children: <Widget>[
Icon(Icons.camera_alt),
Text(
"Choose File",
style: TextStyle(fontSize: 12.0),
textAlign: TextAlign.end,
)
]),
borderSide: BorderSide(color: Colors.pink),
textColor: Colors.pinkAccent,
padding: EdgeInsets.all(10.0),
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(10.0),
)),
Expanded(
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: img.length,
itemBuilder: (BuildContext c, int position) {
return (Image.file(
img[position],
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
));
},
),
),
],
),
),
);
}
}
if you want to use a method in different pages you can use Providers

How to update UI or change state in a widget based on an action in another widget?

I'm trying my hands on a music app using flutter. When an item/music card is tapped, the icon changes to the pause icon to show that its playing. But when i tap card-1, the icon changes(as it should), but when i tap card-2 or any other card, that card also changes icon but card-1 still has the pause icon. How do i change the icon of card-1 to default icon when any other card is tapped?
Currently im using a ListView.builder() to list out all the music cards. the actual card is built with a stateful widget in another file. and the state management is done in that file.
ListView in main.dart
ListView.builder(
shrinkWrap: true,
controller: ScrollController(),
itemCount: allPodcasts.length,
itemBuilder: (BuildContext context, index){
return LongCard(podcast: allPodcasts[index]);
},
)
longcard.dart
class LongCard extends StatefulWidget {
final Podcast podcast;
LongCard({this.podcast});
#override
_LongCardState createState() => _LongCardState();
}
class _LongCardState extends State<LongCard> {
bool playingState;
#override
void initState() {
super.initState();
setState((){
playingState = false;
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: 15.0),
child: InkWell(
onTap: (){
setState((){
if(playingState){
playingState = false;
}else{
playingState = true;
}
});
},
child: Card(
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
elevation: 1.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.only(topLeft: Radius.circular(10.0), bottomLeft: Radius.circular(10.0) ),
child: Image(
image: AssetImage(widget.podcast.image),
height: 100.0,
width: 100.0,
fit: BoxFit.fill
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 15.0, vertical: 20.0),
child: Align(
alignment: Alignment.topLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
widget.podcast.date,
style: Theme.of(context).textTheme.body1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height:5.0),
Text(
widget.podcast.title,
style: Theme.of(context).textTheme.display1,
overflow: TextOverflow.ellipsis,
),
],
),
)
),
Expanded(
child: SizedBox()
),
Container(
alignment: Alignment.centerRight,
height: 100.0,
width: 70.0,
decoration: BoxDecoration(
color: lightCoral,
borderRadius: BorderRadius.only(topRight: Radius.circular(10.0), bottomRight: Radius.circular(10.0) ),
),
child: Center(
child: Icon(
(playingState == true ) ? Icons.pause : Icons.headset,
size: 40.0
)
)
),
],
)
),
),
);
}
I expect that on tap on one card the icons in the other cards are changed to the default icon and then the icon on the tapped card is changed to indicate that its active.
Manage the state of the list from the parent widget that contains the ListView.
Your LongCard widget should be a stateless widget that only displays the data, not manages it. It will only tell the parent widget to switch to another index on press.
class Page extends StatefulWidget {
#override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> {
// List index of the podcast that is playing right now
int activeIndex;
void _setActivePodcast(int index) {
setState(() {
activeIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Container(
child: ListView.builder(
shrinkWrap: true,
controller: ScrollController(),
itemCount: allPodcasts.length,
itemBuilder: (BuildContext context, index) {
return LongCard(
podcast: allPodcasts[index],
listIndex: index,
isPlaying: activeIndex == index,
onPress: _setActivePodcast,
);
},
),
);
}
}
class LongCard extends StatelessWidget {
final Podcast podcast;
final bool isPlaying;
final int listIndex;
final Function(int index) onPress;
const LongCard({
Key key,
this.podcast,
this.listIndex,
this.onPress,
this.isPlaying: false,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: 15.0),
child: InkWell(
onTap: () => onPress(listIndex),
child: Card(
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
elevation: 1.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
bottomLeft: Radius.circular(10.0)),
child: Image(
image: AssetImage(podcast.image),
height: 100.0,
width: 100.0,
fit: BoxFit.fill),
),
Padding(
padding:
EdgeInsets.symmetric(horizontal: 15.0, vertical: 20.0),
child: Align(
alignment: Alignment.topLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
podcast.date,
style: Theme.of(context).textTheme.body1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 5.0),
Text(
podcast.title,
style: Theme.of(context).textTheme.display1,
overflow: TextOverflow.ellipsis,
),
],
),
)),
Expanded(child: SizedBox()),
Container(
alignment: Alignment.centerRight,
height: 100.0,
width: 70.0,
decoration: BoxDecoration(
color: lightCoral,
borderRadius: BorderRadius.only(
topRight: Radius.circular(10.0),
bottomRight: Radius.circular(10.0)),
),
child: Center(
child: Icon(
isPlaying ? Icons.pause : Icons.headset,
size: 40.0,
),
),
),
],
)),
),
);
}
}
You can't dynamically change the icon like that because as it is already defined in the Icon() widget. The Icon() widget is not interactive as explained in the documentation.
https://api.flutter.dev/flutter/widgets/Icon-class.html
You should change Icon() to IconButton() and then you can change the icon inside dynamically using iconData similar to this:
How to change IconButton's icon from callback function in flutter
Or you should return two different Icon() types depending on the value of the boolean playingState
child: Center(
child: playingState ? Icon(Icons.pause, size: 40.0) : Icon(Icons.headset, size: 40.0)
)

Categories

Resources