Getting Album Artwork in flutter - android

I am trying to create a music app. I managed to get all the songs list but I cannot seem to get album artwork from the song metadata. I am using flutter_audio_query: ^0.3.5+6 plugin for this.
I cannot get the artwork by using songs[index].albumArtwork. It always returns null instead of the image path. What is the problem with my code?
Here is my code
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_audio_query/flutter_audio_query.dart';
class HomePage extends StatefulWidget {
const HomePage({Key key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final FlutterAudioQuery audioQuery = FlutterAudioQuery();
List<SongInfo> songs = [];
#override
void initState() {
super.initState();
checkPermission();
getAllSongs();
}
Future<void> getAllSongs() async {
songs = await audioQuery.getSongs();
}
Future<void> checkPermission() async {
if (await Permission.storage.request().isGranted) {
setState(() {});
} else {
_showDialog();
}
}
void _showDialog() async {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Warning'),
content: SingleChildScrollView(
child: ListBody(
children: const <Widget>[
Text('The app needs storage permission in order to work'),
],
),
),
actions: <Widget>[
TextButton(
child: const Text('OK'),
onPressed: () async {
Navigator.of(context).pop();
await checkPermission();
},
),
],
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF3C3C3C),
appBar: AppBar(
elevation: 0.0,
centerTitle: true,
toolbarHeight: 64.0,
leading: Icon(
Icons.music_note_rounded,
),
actions: [
IconButton(
onPressed: () {},
icon: Icon(Icons.search_rounded),
),
],
backgroundColor: Colors.transparent,
title: Text(
"All Songs",
style: GoogleFonts.expletusSans(
fontWeight: FontWeight.bold,
),
),
),
body: ListView.builder(
itemCount: songs.length,
itemBuilder: (context, index) {
return ListTile(
leading: Image.asset(
songs[index].albumArtwork != null
? songs[index].albumArtwork
: "assets/placeholder.png",
),
title: Text(songs[index].title),
subtitle: Text(songs[index].artist),
);
},
),
);
}
}

Sometimes albumArtwork will return a null value. In this case you need to use [FlutterAudioQuery().getArtwork()].
Documentation
Use ResourceType.ALBUM to get album image and ResourceType.SONG to song image.
Example:
// check if artistArtPath isn't available.
(song.artwork == null)
? FutureBuilder<Uint8List>(
future: audioQuery.getArtwork(
type: ResourceType.SONG, id: song.id),
builder: (_, snapshot) {
if (snapshot.data == null)
return Container(
height: 250.0,
child: Center(
child: CircularProgressIndicator(),
),
);
return CardItemWidget(
height: 250.0,
title: artist.name,
// The image bytes
// You can use Image.memory widget constructor
// or MemoryImage image provider class to load image from bytes
// or a different approach.
rawImage: snapshot.data,
);
}) :
// or you can load image from File path if available.
Image.file( File( artist.artistArtPath ) )

Related

How to change Flutter_blue read and write button designs

Frontend looks of read and write command.
I need to change the design of read and write command on the bluetooth devcie screen.
I used the code from pauldemarco git repository.
But the device screen frontend design does not suit good for my application.
Can anyone share how to change the design of upload and download signal button on the user interface?
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'widgets.dart';
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:dproject/qrcode.dart';
void main() {
// RenderErrorBox.backgroundColor = Colors.transparent;
ErrorWidget.builder = (FlutterErrorDetails details) => Scaffold(body:Center(child: Text("Click the Refresh Button"),));
runApp(FlutterBlueApp());
// static ErrorWidgetBuilder builder = _defaultErrorWidgetBuilder;
// RenderErrorBox.textStyle = ui.TextStyle(color: Colors.transparent);
}
class FlutterBlueApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
color: Colors.lightBlue,
home: StreamBuilder<BluetoothState>(
stream: FlutterBlue.instance.state,
initialData: BluetoothState.unknown,
builder: (c, snapshot) {
final state = snapshot.data;
if (state == BluetoothState.on) {
return FindDevicesScreen();
}
return BluetoothOffScreen(state: state);
}),
);
}
}
class BluetoothOffScreen extends StatelessWidget {
const BluetoothOffScreen({Key? key, this.state}) : super(key: key);
final BluetoothState? state;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.lightBlue,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.bluetooth_disabled,
size: 200.0,
color: Colors.white54,
),
Text(
'Bluetooth Adapter is ${state != null ? state.toString().substring(15) : 'not available'}.',
// style: Theme.of(context)
// .primaryTextTheme
// .subhead
// ?.copyWith(color: Colors.white),
),
],
),
),
);
}
}
class FindDevicesScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Find Devices'),
),
body: RefreshIndicator(
onRefresh: () =>
FlutterBlue.instance.startScan(timeout: Duration(seconds: 8)),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
StreamBuilder<List<BluetoothDevice>>(
stream: Stream.periodic(Duration(seconds: 2))
.asyncMap((_) => FlutterBlue.instance.connectedDevices),
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data!
.map((d) => ListTile(
// title: Text(d.toString()),
// subtitle: Text(d.id.toString()),
trailing: StreamBuilder<BluetoothDeviceState>(
stream: d.state,
initialData: BluetoothDeviceState.disconnected,
builder: (c, snapshot) {
if (snapshot.data ==
BluetoothDeviceState.connected) {
return ElevatedButton(
child: Text('OPEN'),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
DeviceScreen(device: d))),
);
}
return Text(snapshot.data.toString());
},
),
))
.toList(),
),
),
StreamBuilder<List<ScanResult>>(
stream: FlutterBlue.instance.scanResults,
initialData: [],
builder: (c, snapshot){
List<ScanResult> data = [];
// print("sh");
for(int i=0;i<snapshot.data!.length;i++)
{
// print("sh");
// print(snapshot.data![i]);
if(snapshot.data![i].device.id.toString()=='FC:67:78:5D:96:EF'){
data.add(snapshot.data![i]);
}
}
// print(data);
return Column(
children: data
.map(
(r) => ScanResultTile(
result: r,
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
r.device.connect();
return DeviceScreen(device: r.device); //FlutterBlueApp();
})),
),
)
.toList(),
);
},
),
],
),
),
),
floatingActionButton: StreamBuilder<bool>(
stream: FlutterBlue.instance.isScanning,
initialData: false,
builder: (c, snapshot) {
if (snapshot.data!) {
return FloatingActionButton(
child: Icon(Icons.stop),
onPressed: () => FlutterBlue.instance.stopScan(),
backgroundColor: Colors.red,
);
} else {
return FloatingActionButton(
child: Icon(Icons.search),
onPressed: () => FlutterBlue.instance
.startScan(timeout: Duration(seconds: 8)));
}
},
),
);
}
}
class DeviceScreen extends StatelessWidget {
const DeviceScreen({Key? key, required this.device}) : super(key: key);
final BluetoothDevice device;
List<int> _getRandomBytes() {
final math = Random();
return [
math.nextInt(255),
math.nextInt(255),
math.nextInt(255),
math.nextInt(255)
];
}
List<Widget> _buildServiceTiles(List<BluetoothService> services) {
List<BluetoothService> data = [];
print("sh%%%%%%%%%%%%%%%%%%%%%%####");
for(int i=0;i<services.length;i++)
{
print("sh####################################");
// print(services[i]);
if(i==2){
data.add(services[i]);
}
}
// print(data);
return data //services
.map(
(s) => ServiceTile(
service: s,
characteristicTiles: s.characteristics
.map(
(c) => CharacteristicTile(
characteristic: c,
onReadPressed: () {
c.read();
},
onWritePressed: () async {
await c.write([1], withoutResponse: false);
await c.read();
// ElevatedButton(
// // style: style,
// onPressed: null,
// child: const Text('Disabled'),
// );
},
onNotificationPressed: () async {
await c.setNotifyValue(!c.isNotifying);
await c.read();
},
descriptorTiles: c.descriptors
.map(
(d) => DescriptorTile(
descriptor: d,
onReadPressed: () => d.read(),
onWritePressed: () => d.write([1]),
),
)
.toList(),
),
)
.toList(),
),
)
.toList();
// print(c.read);
}
// #override
// State<DeviceScreen> createState() => _DeviceScreenState();
// }
// class _DeviceScreenState extends State<DeviceScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("BITLOCK"),//device.name
actions: <Widget>[
StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (c, snapshot) {
VoidCallback? onPressed;
String text;
switch (snapshot.data) {
case BluetoothDeviceState.connected:
onPressed = () => device.disconnect();
text = 'DISCONNECT';
break;
case BluetoothDeviceState.disconnected:
onPressed = () => device.connect();
text = 'CONNECT';
break;
default:
onPressed = null;
text = snapshot.data.toString().substring(21).toUpperCase();
break;
}
return TextButton(
onPressed: onPressed,
child: Text(
text,
style: Theme.of(context)
.primaryTextTheme
.button
?.copyWith(color: Colors.white),
));
},
)
],
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (c, snapshot) => ListTile(
leading: (snapshot.data == BluetoothDeviceState.connected)
? Icon(Icons.bluetooth_connected)
: Icon(Icons.bluetooth_disabled),
title: Text(((){
if(snapshot.data.toString().split('.')[1]=="connected"){
return 'Your Lock is ${snapshot.data.toString().split('.')[1]} to your device.';
}
return 'Your Lock is ${snapshot.data.toString().split('.')[1]} from your device.';
})()),
// subtitle: Text('${device.id}'),
trailing: StreamBuilder<bool>(
stream: device.isDiscoveringServices,
initialData: false,
builder: (c, snapshot) => IndexedStack(
index: snapshot.data! ? 1 : 0,
children: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () => device.discoverServices(),
),
IconButton(
icon: SizedBox(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.grey),
),
width: 18.0,
height: 18.0,
),
onPressed: null,
)
],
),
),
),
),
StreamBuilder<int>(
stream: device.mtu,
initialData: 0,
builder: (c, snapshot) => ListTile(
// title: Text('MTU Size'),
// subtitle: Text('${snapshot.data} bytes'),
trailing: IconButton(
icon: Icon(Icons.lock_open),
onPressed: () => device.requestMtu(223),
),
),
),
StreamBuilder<List<BluetoothService>>(
stream: device.services,
initialData: [],
builder: (c, snapshot) {
return Column(
children: _buildServiceTiles(snapshot.data!),
);
},
),
],
),
),
);
}
}
Thanks in advance.
Here is the solution I found.
You will find widgets.dart in the lib folder in which you are working with main.dart and other files.
You can edit this file according to required design.
In my case, I need to change Iconbutton icon in characteristics tile to change the design of read and write button.

Making spinner background transparent in flutter

I'm new to flutter and making my first webview app. Here I'm trying to add a spinner every time when a user tries to click the link or page load. I want to make spinner background opacity a bit low just like the given example, but opacity doesn't work at all.
My approach
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.white.withOpacity(0.5),
child: Center(
child: SpinKitDualRing(
color: Colors.pinkAccent,
size: 45.0,
controller: AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
),
),
),
)
I'm using here flutter_spinkit package as a spinner.
Full code
// ignore_for_file: prefer_const_constructors
// ignore: use_key_in_widget_constructors
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:splash_screen_view/SplashScreenView.dart';
void main(){
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Color(0xff1e2229)
));
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
Widget spalshfirst = SplashScreenView(
navigateRoute: WebViewClass(),
duration: 3000,
imageSize: 80,
imageSrc: 'assets/splash.png',
text: "Food Delivery",
textType: TextType.TyperAnimatedText,
textStyle: TextStyle(
fontSize: 25.0,
),
colors: const [
Colors.purple,
Colors.blue,
Colors.yellow,
Colors.red,
],
backgroundColor: Colors.white,
);
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: spalshfirst
)
);
}
}
class WebViewClass extends StatefulWidget {
WebViewState createState() => WebViewState();
}
class WebViewState extends State<WebViewClass> with TickerProviderStateMixin{
late WebViewController _controller;
final Completer<WebViewController> _controllerCompleter =
Completer<WebViewController>();
//Make sure this function return Future<bool> otherwise you will get an error
Future<bool> _onWillPop(BuildContext context) async {
if (await _controller.canGoBack()) {
_controller.goBack();
return Future.value(false);
} else {
return Future.value(true);
}
}
#override
void initState() {
super.initState();
// Enable hybrid composition.
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
bool isLoading = false;
final key = UniqueKey();
int position = 0;
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _goBack(context),
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: null,
body: SafeArea(
child: IndexedStack(
index: position,
children: [
WebView(
initialUrl: 'https://google.com',
javascriptMode: JavascriptMode.unrestricted,
key: key,
onPageStarted: (value) {
setState(() {
position = 1;
});
},
onPageFinished: (value) {
setState(() {
position = 0;
});
},
onWebViewCreated: (WebViewController webViewController) {
_controllerCompleter.future
.then((value) => _controller = value);
_controllerCompleter.complete(webViewController);
},
),
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.white.withOpacity(0.5),
child: Center(
child: SpinKitDualRing(
color: Colors.pinkAccent,
size: 45.0,
controller: AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
),
),
),
)
],
),
),
),
);
}
Future<bool> _goBack(BuildContext context) async {
if (await _controller.canGoBack()) {
_controller.goBack();
return Future.value(false);
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Do you want to exit from Foodrive?'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('No'),
),
TextButton(
onPressed: () {
SystemNavigator.pop();
},
child: Text('Yes'),
),
],
));
return Future.value(true);
}
}
}
Since the container is containing only the spinner, and not the background widget, settings its opacity won't work,
I'd suggest using the Stack widget with the Opacity widget
Something like this (just a reference point):
return Stack(children: [
Opacity(opacity: 0.5, child: resetOfTheWidgetTree),
Container(child: spinWidgetHere),
]);

Setting up Stream for real-time user data

I'm trying to set up a stream such that if a user updates their data(like their name), it's displayed in real-time. Currently, It's already being updated in the firestore collection but I have to reload the in-app home page to actually see the updated value. I'm also using a custom UserData class.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final AuthService _auth = AuthService();
User user = FirebaseAuth.instance.currentUser;
DatabaseService db = DatabaseService(uid: FirebaseAuth.instance.currentUser.uid);
void _showSettingsPanel(){
showModalBottomSheet(context: context, builder: (context){
return Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 60.0),
child: SettingsForm(),
);
});
}
#override
Widget build(BuildContext context) {
return StreamProvider<UserData>.value( // need to setup this stream
value: DatabaseService().userData,
child: Scaffold(
backgroundColor: Colors.redAccent,
appBar: AppBar(
title: Text('Signed in'),
backgroundColor: Colors.blueAccent,
elevation: 0.0, //no drop shadow
actions: <Widget>[
FlatButton.icon(
onPressed: () async {
await _auth.signOutUser();
},
icon: Icon(Icons.person),
label: Text('logout')),
FlatButton.icon(
icon: Icon(Icons.settings),
label: Text('Settings'),
onPressed: () => _showSettingsPanel(),
)
],
),
body: FutureBuilder(
future: db.getProfile(),
builder: (context, snapshot){
if (snapshot.connectionState == ConnectionState.done){
return ListTile(
leading: CircleAvatar(
radius: 25.0,
backgroundColor: Colors.brown,
),
title: Text(snapshot.data),
tileColor: Colors.white,
);
}
else {
return CircularProgressIndicator();
}
},
)
),
);
}
}
No need for a StreamProvider, you just need a StreamBuilder!
You already have the uid, so you just have to listen to the stream from Firestore.
Here is an example.
StreamBuilder<DocumentSnapshot>(
stream:
FirebaseFirestore.instance.collection('users').doc(uid).snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.active) {
return Center(child: CircularProgressIndicator());
}
return ListTile(
leading: CircleAvatar(
radius: 25.0,
backgroundColor: Colors.brown,
),
title: Text(snapshot.data.data()['name']),
tileColor: Colors.white,
);
},
);

Flutter update Reorderable ListView when checkbox in AlertDialog check or unchecked

I'm currently working on a Flutter mobile app which is supposed to work on Android and IOS.
The issue I'm having is about ListView and updating it.
I know I'm doing wrong with a lot of things but I'm learning and I would like to learn properly. So if you have any comments, tips about the code pls give them :)
Basically here is what it does :
The main player will choose who will play with him at the game via an AlertDialog which has a CheckboxList inside it and every time he selects a player, it will update a list called choosenPlayers which has all Player objects choosen in it.
Then what I want to do is to display a list of all selected players (a reorderable list to change the order of players) and update it everytime the choosenPlayers list is updated.
I managed to display these players but I have to reload the page by going in the drawer menu and clicking on page link to see added players.
I use a stateful widget for my players reorderable list and I pass to the parent the list of players (This is not the rigth way to do it I know) :
import 'package:flutter/material.dart';
import 'package:mollky/models/player.dart';
class ChoosenPlayers extends StatefulWidget {
_ChoosenPlayersState _choosenPlayersState = _ChoosenPlayersState();
List<Player> choosenPlayers = [];
ChoosenPlayers({Key key, this.choosenPlayers}) : super(key: key);
#override
_ChoosenPlayersState createState() => _choosenPlayersState;
}
class _ChoosenPlayersState extends State<ChoosenPlayers> {
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return ReorderableListView(
onReorder: onReorder,
children: getListItems(),
);
}
List<ListTile> getListItems() => widget.choosenPlayers
.asMap()
.map((i, item) => MapEntry(i, buildTenableListTile(item, i)))
.values
.toList();
ListTile buildTenableListTile(Player item, int index) {
return ListTile(
key: ValueKey(item.id),
title: Text(item.nickname + " " + item.name),
leading: Text("#${index + 1}"),
);
}
void onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
setState(() {
Player reOrderedPlayer = widget.choosenPlayers[oldIndex];
widget.choosenPlayers.removeAt(oldIndex);
widget.choosenPlayers.insert(newIndex, reOrderedPlayer);
});
}
}
Here is the code of the main page where reorderable list is displayed and AlertDialog showed.
Sorry, couldn't format with Dart, don't run the code snipped obviously xD
class NewGame extends StatefulWidget {
#override
State<StatefulWidget> createState() => NewGameState();
}
class NewGameState extends State<NewGame> {
List<Player> players = [];
NewGameState() {
this.players.add(
Player(id: 0, name: "Dupont", nickname: "julien", picture: "test"));
this
.players
.add(Player(id: 1, name: "Dpont", nickname: "julien", picture: "test"));
this
.players
.add(Player(id: 2, name: "Dunt", nickname: "juen", picture: "test"));
}
static List<Player> _choosenPlayers = [];
ChoosenPlayers choosenPlayersObject = ChoosenPlayers(
choosenPlayers: _choosenPlayers,
);
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: DrawerWidget(),
appBar: AppBar(title: Text("Nouvelle partie")),
body: Column(children: <Widget>[
Card(
child: ListTile(
leading: Icon(Icons.people),
title: Text("Choisissez les joueurs"),
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Les joueurs existants"),
content:
Stack(overflow: Overflow.visible, children: <
Widget>[
Positioned(
right: -40.0,
top: -40.0,
child: InkResponse(
onTap: () {
Navigator.of(context).pop();
},
child: CircleAvatar(
child: Icon(Icons.close),
backgroundColor: Colors.lightBlue,
),
),
),
Positioned(
child: StatefulBuilder(
builder: (BuildContext context,
StateSetter setState) {
return Container(
width: 350.0,
height: 150.0,
child: ListView.builder(
itemCount: players.length,
itemBuilder:
(context, playerIndex) {
return CheckboxListTile(
title: Text(players[playerIndex]
.nickname +
" " +
players[playerIndex].name),
value: _choosenPlayers.contains(
players[playerIndex]),
onChanged: (bool value) {
if (!_choosenPlayers.contains(
players[playerIndex])) {
_choosenPlayers.add(
players[playerIndex]);
setState(() {});
} else {
_choosenPlayers.remove(
players[playerIndex]);
setState(() {});
}
},
secondary: const Icon(
Icons.hourglass_empty),
);
}),
);
},
),
),
]));
});
})),
Container(
width: 350.0,
height: 150.0,
child: choosenPlayersObject,
),
]));
}
}
I've seen nothing on forums about updating list without triggering a callback like onRefresh which is not what I want.
It is a real nightmare xD. Sorry for french words btw I can translate if needed but they are not important, simple text.
Here are two screenshots of the list and alert dialog :
Thank you in advance :)
The state of parent widget is not updated. That's why, even though the payer is added to the list. But not shown to in parent widget.
The setState you called only update the state of StatefulBuilder not of the NewGame.
Check out the below code.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: NewGameScreen(),
);
}
}
class NewGameScreen extends StatefulWidget {
#override
_NewGameScreenState createState() => _NewGameScreenState();
}
class _NewGameScreenState extends State<NewGameScreen> {
List<Player> _availablePlayers = [];
List<Player> _selectedPlayers = [];
#override
void initState() {
super.initState();
_availablePlayers = [
Player(id: 0, name: "Ross", nickname: "Geller", picture: "test"),
Player(id: 1, name: "Rachel", nickname: "Green", picture: "test"),
Player(id: 2, name: "Chandler", nickname: "Bing", picture: "test"),
];
}
_selectPlayer() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Existing players"),
content: Stack(
overflow: Overflow.visible,
children: <Widget>[
Positioned(
right: -40.0,
top: -40.0,
child: InkResponse(
onTap: () {
Navigator.of(context).pop();
},
child: CircleAvatar(
child: Icon(Icons.close),
backgroundColor: Colors.lightBlue,
),
),
),
StatefulBuilder(
builder: (BuildContext context, StateSetter alertState) {
return Container(
width: 350.0,
height: 150.0,
child: ListView.builder(
itemCount: _availablePlayers.length,
itemBuilder: (context, playerIndex) {
return CheckboxListTile(
title:
Text(_availablePlayers[playerIndex].nickname + " " + _availablePlayers[playerIndex].name),
value: _selectedPlayers.contains(_availablePlayers[playerIndex]),
onChanged: (bool value) {
if (_selectedPlayers.contains(_availablePlayers[playerIndex])) {
_selectedPlayers.remove(_availablePlayers[playerIndex]);
} else {
_selectedPlayers.add(_availablePlayers[playerIndex]);
}
setState(() {});//ALSO UPDATE THE PARENT STATE
alertState(() {});
},
secondary: const Icon(Icons.hourglass_empty),
);
},
),
);
},
),
],
),
);
},
);
}
_onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
print('oldIndex:$oldIndex');
print('newIndex:$newIndex');
setState(() {
Player player = _selectedPlayers[newIndex];
_selectedPlayers[newIndex] = _selectedPlayers[oldIndex];
_selectedPlayers[oldIndex] = player;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("New Game")),
body: Column(
children: <Widget>[
Card(
child: ListTile(
leading: Icon(Icons.people),
title: Text("Choose players"),
onTap: _selectPlayer,
),
),
Flexible(
child: ReorderableListView(
onReorder: _onReorder,
children: _selectedPlayers.map((player) {
return ListTile(
key: ValueKey(player.id),
title: Text(player.nickname + " " + player.name),
leading: Text("#${_selectedPlayers.indexOf(player) + 1}"),
);
}).toList(),
),
),
],
),
);
}
}
class Player {
int id;
String name;
String nickname;
String picture;
Player({this.id, this.name, this.nickname, this.picture});
}
Hope it helps :)

Flutter app not keeping the settings made in the app after reopening the app

I have an app which has two tabs. One of the which is the "SAVED ITEMS" tab. When I save the items (from a different screen of ALL ITEMS LIST) it gets saved and on even switching the tabs works fine. But when I close the app and reopen it, the "SAVED ITEMS" list is empty and I've to select the items again. I've used the AutomaticKeepAliveClientMixin but its not helping. Any idea on how to solve this folks?
My code:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with AutomaticKeepAliveClientMixin {
#override
Widget build(BuildContext context) {
super.build(context);
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.blue,
accentColor: Colors.white,
),
home: DefaultTabController(
length: 2,
child: Scaffold(
drawer: Drawer(),
backgroundColor: Colors.blueAccent,
appBar: AppBar(
backgroundColor: Colors.blueAccent,
title: Text('AIO'),
bottom: TabBar(
tabs: <Widget>[
Tab(icon: Icon(Icons.search)),
Tab(icon: Icon(Icons.favorite)),
],
),
),
body: TabBarView(
children: <Widget>[
gridView,
SecondPage(),
],
),
),
),
);
}
#override
bool get wantKeepAlive => true;
}
SecondTab code:
Set<int> favorites = {};
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage>
with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Stack(
fit: StackFit.expand,
children: <Widget>[
_getFavoriteList(),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.blue,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditFavorites(),
),
).then((updatedFavorites) {
if (updatedFavorites != null)
// setState(() {
favorites = updatedFavorites;
// });
});
},
),
),
)
],
);
}
Add shared_preferences dependency in pubspec.yaml
Update your main()
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences pref = await SharedPreferences.getInstance();
pref.getStringList("favorites")?.forEach((fav){
favorites.add(int.tryParse(fav));
});
runApp(MyApp());
}
then update your SecondPage
Set<int> favorites = {};
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
Future<void> _upDateFavorites(Set<int> updatedFavorites) async {
print("secong: u$updatedFavorites");
SharedPreferences pref = await SharedPreferences.getInstance();
List<String> favoritesAsString =
updatedFavorites.map((fav) => fav.toString()).toList(); //TODO: Change `favorites` to `updatedFavorites`
print(favoritesAsString);
await pref.setStringList("favorites", favoritesAsString); //TODO: await here to store it completely
favorites = updatedFavorites;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: <Widget>[
_getFavoriteList(),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.blue,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditFavorites(),
),
).then((updatedFavorites) async {
if (updatedFavorites != null)
// setState(() {
_upDateFavorites(updatedFavorites);
// });
});
},
),
),
)
],
);
}
Widget _getFavoriteList() {
if (favorites?.isNotEmpty == true)
return _FavoriteList();
else
return _EmptyFavoriteList();
}
}
Don't forget to import 'package:shared_preferences/shared_preferences.dart'; where you use SharedPreferences
Avoid storing widget in a vaiable, if it needs to be rebuild.
So delete var favGridView = GridView.builder( ....
class _FavoriteList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: favorites.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
print("kkkkkkkkkkk: ${favorites.elementAt(index)}");
return InkWell(
child: Card(
elevation: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10),
child: Container(
child: Image.asset(
'lib/images/${images[favorites.elementAt(index)]}'), //TODO: Change this
// child: SizedBox(child: Text('yashjha'),),
// decoration: BoxDecoration(
// image: DecorationImage(
// image: AssetImage('lib/images/${images[index]}'),
// fit: BoxFit.fitWidth,
// alignment: Alignment.topCenter,
// ),
// ),
),
),
Text(nameOfSite[favorites.elementAt(index)]), //TODO: Change This
],
),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Text("dsadsa")),
);
},
);
},
);
}
}
I also recommend using actions instead of FloatingButton in _EditFavoritesState. Because the floating button hides last item. which makes it unable to add to favorites
AutomaticKeepAliveClientMixin is used to retain the data when the app is running and you need to retain data while switching between tabs, pages etc.
I would suggest you use SharedPreferences to easily save data to your memory that can be retrieved when the app is started again.
SharedPreferences: https://pub.dev/packages/shared_preferences

Categories

Resources