I'm using a listview to display images and text in my flutter app. I've stored the asset path and text in a Json file and I convert it to a list. Getting the image asset path and displaying the correct one seems to work with no issues but thats not the case in playing the audio files from their assets.
I'm using this package import 'package:assets_audio_player/assets_audio_player.dart';
declaration final AssetsAudioPlayer playAudio = AssetsAudioPlayer();
and this is main widget
#override
Widget build(BuildContext context) {
Widget _buildRow(int idx) {
for (var translations in widget.category.translations) {
_wordList = widget.category.translations[idx];
return Container(
height: 88.0,
child: Card(
child: ListTile(
onTap: () {
playAudio.open(
Audio(_wordList.audio),
);
// player.play(_wordList.audio);
log(_wordList.audio, name: 'my.other.category');
},
onLongPress: () {},
leading: SizedBox(
width: 50.0,
height: 88.0,
child: Image(
image: AssetImage(_wordList.emoji),
fit: BoxFit.contain,
),
),
title: Text(
_wordList.akan,
style: TextStyle(fontSize: 18),
),
subtitle: Text(
_wordList.english,
style: TextStyle(fontSize: 18, color: Colors.black),
),
trailing: const Icon(Icons.play_arrow, size: 28),
),
),
);
}
}
Since the image assets in the json file have no issues I don't get why the audio does
I've stored them like this,
{
"english": "mother",
"akan": "ɛna",
"emoji": "assets/icons/family_mother.png",
"audio": "assets/audio/family_mother.mp3"
},
Solved it by generating a new listtile widget through iteration and then putting it into a listview
Related
I'm using the flutter markdown package made by the flutter team here https://pub.dev/packages/flutter_markdown. I've created my own MarkdownElementBuilder based on their examples that inserts my own custom widget into the markdown and it looks like this:
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:outlit_app/constants/color_theme.dart';
import 'package:outlit_app/constants/dimension.dart';
import 'package:outlit_app/models/models.dart';
import 'package:markdown/markdown.dart' as md;
class DefinitionBuilder extends MarkdownElementBuilder {
final List<Definition> definitions;
DefinitionBuilder(this.definitions) : super();
#override
Widget visitElementAfter(md.Element element, TextStyle preferredStyle) {
final String textContent = element.textContent;
Definition definition = definitions.firstWhere(
(def) => textContent.toLowerCase().contains(def.word.toLowerCase()),
orElse: () =>
Definition(word: 'nothing found for $textContent', definition: ''),
);
return Tooltip(
margin: EdgeInsets.all(Dimensions.MARGIN_SIZE_EXTRA_LARGE),
padding: EdgeInsets.all(Dimensions.PADDING_SIZE_DEFAULT),
decoration: BoxDecoration(
color: GetColor.gradientPurple,
borderRadius: BorderRadius.circular(8),
),
verticalOffset: -10,
triggerMode: TooltipTriggerMode.tap,
message: definition.definition.trim(),
child: Text(
textContent.trim(),
style: TextStyle(
color: GetColor.primaryColor,
fontSize: Dimensions.FONT_SIZE_OVER_LARGE,
),
),
);
}
}
class DefinitionSyntax extends md.InlineSyntax {
static final String AST_SYMBOL = 'def';
DefinitionSyntax() : super(_pattern);
static const String _pattern = r'{{(.*)}}';
#override
bool onMatch(md.InlineParser parser, Match match) {
parser.addNode(md.Element.text(AST_SYMBOL, match[1]));
return true;
}
}
It works well but the widget is always on it's own seperate line as opposed to being inline with the rest of the text. If I return a simple text widget I still get the same thing.
Any tips in the right direction would be great :)
I got it work although not perfect because the leading distribution is a little off with the text of the tooltip but the widget that gets embedded now looks like this:
return RichText(
text: TextSpan(
children: [
WidgetSpan(
child: Container(
child: Tooltip(
margin: EdgeInsets.all(Dimensions.MARGIN_SIZE_EXTRA_LARGE),
padding: EdgeInsets.all(Dimensions.PADDING_SIZE_DEFAULT),
decoration: BoxDecoration(
color: GetColor.gradientPurple,
borderRadius: BorderRadius.circular(8),
),
verticalOffset: -10,
triggerMode: TooltipTriggerMode.tap,
message: definition.definition.trim(),
child: Text(
textContent.trim(),
style: TextStyle(
color: GetColor.primaryColor,
fontSize: Dimensions.FONT_SIZE_OVER_LARGE,
leadingDistribution: TextLeadingDistribution.even,
height: 1,
),
),
),
))
],
),
);
I am trying to create a favorite button for my app. Which work is to change and save color, while the user press it, So I decided to use hive db for it. The problem is, when i tap on the button; the color get changed, but when i move to other page or hot start/reload the page, the color changed back to it former self automatically.How to solve this problem and create a favorite button successfully.
class p1 extends StatefulWidget {
#override
_p1State createState() => _p1State();
}
class _p1State extends State<p1> {
Box box;
_p1State();
#override
void initstate(){
super.initState();
// Get reference to an already opened box
box = Hive.box(FAVORITES_BOX);
}
#override
void dispose() {
// Closes all Hive boxes
Hive.close();
super.dispose();
}
get_info(){
var info = box.get(_isFavorite);
}
var _isFavorite = true;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body:Stack(
children:<Widget>[
Image(
image:AssetImage("Image/Chowsun1.jpg"),
fit:BoxFit.cover,
width: double.infinity,
height: double.infinity,
),
Align(alignment: Alignment.center,
child: Text(' Rehman '
,style: TextStyle(fontSize: 35.0,
color: Colors.white,
fontFamily: "Explora",
fontWeight: FontWeight.w900 ) )
),
Stack ( children: [Positioned(
top:90,
right: 20,
child:const Text(' 1 ',
style: TextStyle(
fontSize: 25.0,
color: Colors.white,
fontFamily: "Comforter"
),
),
)], ),
Align(
alignment: Alignment.bottomCenter,
child: (
IconButton(
icon: Icon(
Icons.favorite,
color:_isFavorite ? Colors.white: Colors.red
),
onPressed: () {
setState(() {
_isFavorite= !_isFavorite;
});
box.put(!_isFavorite, _isFavorite);
get_info();
}
)
)
)])
),
);
}
}
There are a lot of things that don't make sense in this code.
I believe that the flow should be like this.
Your Widget first initialize the hive box and check if there is any data to refer to.
If there is a data already written, load it, or else, set a default value, which I bet is false. Because everything is not favorite before you make it so.
When you click Favorite button, it updates _isFavorite value and put it in the open Box with a key which must be a unique value that can represent the selected item right
There are problems in your code:
You initialized the Box in initState() but you didn't consider if there is data already in Box. So you have to check and if there is data you can use, you have to update _isFavorite accordingly.
You close Box every time your widget is disposed but I don't see your widget opens it again when you're back to the widget. So just don't close it. It seems like you'll use it very often.
You put the data with a key, which is the old data whose data type is boolean. It's binary so it can't be used as an unique key.
So if I were you, I would fix the code like following:
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
class p1 extends StatefulWidget {
#override
_p1State createState() => _p1State();
}
class _p1State extends State<p1> {
late Box box;
late bool _isFavorite;
#override
void initState() {
super.initState();
// Get reference to an already opened box.
// This should be open before you re-enter this widget.
box = Hive.box('FAVORITES_BOX');
// Try getting data that already exists
final data = box.get(someKeyYouAlreadyUsed);
// if there is no data about favorite, assign false to _isFavorite
_isFavorite = data ?? false;
}
// Don't close the Hive Box if you're planning to come back and use it soon.
// #override
// void dispose() {
// // Closes all Hive boxes
// Hive.close();
// super.dispose();
// }
// I deleted this function. It doesn't do anything.
// get_info() {
// var info = box.get(_isFavorite);
// }
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(children: <Widget>[
const Image(
image: AssetImage("Image/Chowsun1.jpg"),
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
),
const Align(
alignment: Alignment.center,
child: Text(' Rehman ',
style: TextStyle(
fontSize: 35.0,
color: Colors.white,
fontFamily: "Explora",
fontWeight: FontWeight.w900))),
Stack(
children: const [
Positioned(
top: 90,
right: 20,
child: Text(
' 1 ',
style: TextStyle(
fontSize: 25.0,
color: Colors.white,
fontFamily: "Comforter"),
),
)
],
),
Align(
alignment: Alignment.bottomCenter,
child: (IconButton(
icon: Icon(Icons.favorite,
color: _isFavorite ? Colors.white : Colors.red),
onPressed: () {
setState(() {
_isFavorite = !_isFavorite;
});
box.put(someUniqueValueToRepresentThisItem, _isFavorite);
})))
])),
);
}
}
I hope this would work for you.
You are setting the state of button on the basis of _isFavorite variable value instead of the value which came from the hive database (your get info function). After inserting the value in hive db you must set the state of button using hive db value instead of the _isFavorite variable.
I have a Flutter project in which I am:
Downloading the zip file (full of html files)
Extracting the html files to a new directory (ebooks/02)
Saving the local file urls in a List
Displaying the urls in Webview & iterate through List for back & forth.
However, in the web view all I get is "Unable to load asset..."
Though any standard http url works fine in webview.
I tried from these two answers but no result: Answer1 & Answer2
The exception I get is :
E/flutter (10963): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: Unable to load asset: /data/user/0/com.pts.school_ebook_reader_app_prag/app_flutter/ebooks/04/00.html
I need to understand how to make the local html at the given path display in webview.
Any help would be appreciated.
Edit:
The webview code (currently trying to display only 1st url in list):
class _BookReaderState extends State<BookReader> {
List<String> urls = UserData.ebook;
WebViewController web;
final _key = UniqueKey();
String _url;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
"Book Title Here",
style: GoogleFonts.roboto(
fontWeight: FontWeight.w900,
fontSize: 25.0,
color: Colors.white),
textAlign: TextAlign.center,
),
actions: [
Padding(
padding: EdgeInsets.only(right: 50),
child: IconButton(
icon: Image.asset('images/04_mobile-menu.png'),
color: Colors.red,
alignment: Alignment.centerLeft,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyLibrary_Screen()));
}),
),
Padding(
padding: const EdgeInsets.only(left: 1.0),
child: IconButton(
icon: Image.asset('images/05_mobile-close.png'),
color: Colors.red,
alignment: Alignment.centerRight,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyLibrary_Screen()));
}),
),
],
),
body: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: Container(
width: 700,
height: 490,
child: FutureBuilder<String>(
future: _loadHtmlFromAssets(0),
builder: (context, snapshot) {
if (snapshot.hasData) {
return WebView(
initialUrl: new Uri.dataFromString(snapshot.data,
mimeType: 'text/html')
.toString(),
javascriptMode: JavascriptMode.unrestricted,
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
})),
),
Padding(
padding: EdgeInsets.only(top: 85),
child: Container(
height: 70,
color: Colors.blue,
child: RowSuper(
innerDistance: 50,
children: [
InkWell(
child: Image.asset(
"images/05_mobile-arrow-left.png",
alignment: Alignment.bottomLeft,
height: 170,
width: 90,
),
onTap: () => pageIncDec(1),
),
Text('Page ${urls.indexOf(_url) + 1} of ${urls.length}',
style: GoogleFonts.roboto(
fontWeight: FontWeight.w900,
fontSize: 33.0,
color: Colors.white)),
InkWell(
child: Image.asset(
"images/05_mobile-arrow-right.png",
alignment: Alignment.bottomRight,
height: 270,
width: 90,
),
onTap: () => pageIncDec(2),
),
],
),
),
),
],
));
}
pageIncDec(int i) async {
int n;
if (i == 1) {
setState(() {
urls.indexOf(_url) > 0 ? n = urls.indexOf(_url) - 1 : n = 0;
});
} else {
setState(() {
urls.indexOf(_url) < urls.length
? n = urls.indexOf(_url) + 1
: n = urls.length - 1;
});
}
_url = await _loadHtmlFromAssets(n);
web.loadUrl(_url);
print(_url);
}
Future<String> _loadHtmlFromAssets(int n) async {
String fileText = await rootBundle.loadString(urls[n]);
print(fileText);
String r = (Uri.dataFromString(fileText,
mimeType: 'text/html', encoding: Encoding.getByName('utf-8'))
.toString());
print(r);
return r;
}
Code to add files :
Directory dir =
Directory('${_appDocDir.path}/$folderName/${item.key_name}');
List<FileSystemEntity> listOfAllFolderAndFiles =
await dir.list(recursive: false).toList();
if (UserData.ebook != null) UserData.ebook.clear();
listOfAllFolderAndFiles.forEach((element) {
if (element.toString().contains("html")) {
String url = element.toString().replaceAll("File: ", "");
url = url.replaceAll("'", "");
UserData.ebook.add(url.toString());
}
UserData.eBookTitle = item.title;
});
print(UserData.ebook);
And result of printing UserData.ebook :
I/flutter ( 3465): [/data/user/0/com.pts.school_ebook_reader_app_prag/app_flutter/ebooks/02/00.html, /data/user/0/com.pts.school_ebook_reader_app_prag/app_flutter/ebooks/02/01.html, /data/user/0/com.pts.school_ebook_reader_app_prag/app_flutter/ebooks/02/02.html, /data/user/0/com.pts.school_ebook_reader_app_prag/app_flutter/ebooks/02/03.html, /data/user/0/com.pts.school_ebook_reader_app_prag/app_flutter/ebooks/02/04.html, /data/user/0/com.pts.school_ebook_reader_app_prag/app_flutter/ebooks/02/05.html, /data/user/0/com.pts.school_ebook_reader_app_prag/app_flutter/ebooks/02/06.html]
Checking:
//Checking if file exists
print("File ${UserData.ebook[0]} exists ? " +
File(UserData.ebook[0]).existsSync().toString());
Result:
I/flutter ( 3465): File /data/user/0/com.pts.school_ebook_reader_app_prag/app_flutter/ebooks/02/00.html exists ? true
Finally after trying all possible plugins realized that Flutter webview as of now cannot display local html files that are heavy on css & javascript side.
The same webview can only display external urls or basic html files(minus css & js).
I switched over to native android for this.
I think you should load html as normal file, not like asset, because it's not located in Assets directory and convert it to base64:
Future<String> _loadHtmlFromAssets(int n) async {
final file = File(urls[n]);
String fileText = await file.readAsString();
final base64 = base64Encode(utf8.encode(fileText));
return "data:text/html;base64,$base64";
}
Then show it like:
return WebView(
initialUrl: snapshot.data.toString(),
javascriptMode: JavascriptMode.unrestricted,
);
I know this may be a little late, but it's possible to add an HTML view with complex js and css, it can be done in two methods. The first and really bad looking way is to put all in one file, it will be visible both in iOS and Android and to load it via the WebView, the other method (I'm using this one to load an Angular local web component in an app) is to use the plugin webview_flutter_plus which is an extension of the normal WebView in flutter. This plugin requires to add in the pubspec.yaml all the files needed in the WebComponent, so you can add multiple complex css files and js files.
The tutorial in the plugin is pretty complete.
The only problem I'm facing is with iOS, which doesn't find the files, but that should be caused by a native problem, iOS try to load the files runtime and those are in a different location, so you need to find the correct path and replace it runtime in the html file (that was the solution I've implemented in a native project in swift).
Hope this helped for future projects.
I have implemented an listener in the following way:
#override
void didChangeDependencies() {
final SlotDataProvider slotDevice = Provider.of<SlotDataProvider>(context);
spptask.streamdevice.listen((device) {
setState(() {
slotDevice._devices[0].name = device.name;
print("Device data received: ${device.name} ");
});
}, onError: (error) {
print("Error: $error.message");
});
super.didChangeDependencies();
}
I listen on a splitted controller and the print "Device data received:..." is called but the widget is not actualized. In the build method I do the following:
...
#override
Widget build(BuildContext context) {
final slotProvider = Provider.of<SlotDataProvider>(context);
final deviceProvider = Provider.of<DeviceDataProvider>(context);
Device slotDevice = slotProvider.getDevice(widget.slot);
Device device = deviceProvider.getDevice(widget.slot);
_dropdownMenuItems = buildDropdownMenuItems(deviceProvider.get());
return ListTile(
title: Row(
children: <Widget>[
SizedBox(
width: 140,
child: DropdownButton(
isExpanded: true,
disabledHint: Text(slotDevice.name),
hint: Text(slotDevice.name),
value: device,
items: _dropdownMenuItems,
onChanged: (value) {
device.setDevice(value);
slotDevice.setDevice(value);
}),
),
SizedBox(width: 10),
SizedBox(width: 60, child: Text('SLOT#${slotDevice.slot}')),
],
),
subtitle: Text(slotDevice.bdaddr, style: TextStyle(fontSize: 10.0)),
leading: SizedBox(
height: 40,
width: 35,
child: UsbBatteryImageAsset(slot: widget.slot),
),
trailing: Icon(Icons.keyboard_arrow_right),
);
}
}
What is missing in the above code. The SlotDataProvider is a fix list of "Device" with attributes such as name, id and so on.
#EDIT
The problem has to do with the combobox. If I change an other field, it works.
Usually for widgets to be rebuilt based on the data updated we use streambuilders
this will cause the widget to rebuild every time there is a change in the stream
it seams that your widget is being built once with the first listening of the data
have you tried wrapping the gridview in a stateful builder ?
I'm trying to create stacks of cards in my Flutter project. Each card contains different data/information and when I try visualize with a dummy data, I have to use a lot of variables which is pretty much repeating variable name for each card. Is there aways to make a reusable card component in flutter so that I can make it clear and simple because when I use real data in the future, I might have more than 2 cards in a group and they will also have different data. Any suggestion will be really appreciated.
class MyConstructor {
MyConstructor({this.jonathan1,this.jonathan2,this.jonathan3});
}
class StackedCardsState extends State<HomePage> {
List<MyConstructor> cards = [
MyConstructor(h1: "Hello", h2: "hello3")
];
/////
Padding(
padding: EdgeInsets.all(15.0),
child: Column(children: [
Text(MyConstructor.hey, style: TextStyle(fontWeight: FontWeight.bold),),
Text(MyConstructor.hey),
Text(MyConstructor.hey, style: TextStyle(color: Colors.red[500]),),
VerticalDivider(color: Colors.blue),
])),
Your problem is first of all rather simple, you are violating the DRY concept (Don't repeat yourself, https://en.wikipedia.org/wiki/Don%27t_repeat_yourself ).
As soon as you start copy pasting code take a moment and think about your code and how you can abstract it into a reusable component.
Another big issue that I think you are lacking is variable naming. It is a very very important part of writing code. Might seem trivial but it will be very hard to understand what a variable named cardOne1 and cardTwo2 actually mean. What is the purpose of that variable? What does it do?
Now with that said I understand your app has something to do with car sales but other than that I'm not really sure what I'm looking at. There for I will have a harder time finding a good variable for this code but here is an example.
So lets break down the contents in the card to a single reusable widget, we can also make a data class (or model) for storing the data that we then give to the widget.
//car_details.dart
class CarDetails {
String title;
String diffNumber;
String diffPercent;
Color colorIndicator;
CarDetails({
this.title,
this.diffNumber,
this.diffPercent,
this.colorIndicator,
});
}
//car_card_details.dart
class CarCardDetails extends StatelessWidget {
final double padding;
final CarDetails carDetails;
CarCardDetails({
this.carDetails,
this.padding = 15,
});
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
carDetails.colorIndicator != null
? Container(
color: carDetails.colorIndicator,
height: 60,
width: 2,
)
: Container(),
Padding(
padding: EdgeInsets.all(padding),
child: Column(children: [
Text(carDetails.title),
Text(carDetails.diffNumber),
Text(carDetails.diffPercent),
VerticalDivider(color: Colors.blue),
])),
],
);
}
}
To use this component we make a CarCard Widget that takes a title and a list of CarDetails like so:
// car_card.dart
class CarCard extends StatelessWidget {
final String title;
final List<CarDetails> carDetails;
CarCard({this.title, this.carDetails});
#override
Widget build(BuildContext context) {
List<Widget> detailRow = List();
if (carDetails != null) {
carDetails.forEach((element) {
detailRow.add(CarCardDetails(
top: element.title,
middle: element.diffNumber,
bottom: element.diffPercent,
lineColor: element.colorIndicator,
));
});
}
return Container(
//height: 150, //I would not hardcode the height, let the childrent expand the widget instead
child: SingleChildScrollView(
child: Card(
elevation: 8.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
child: InkWell(
child: Column(children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(children: [
Text(
title,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Spacer(),
Icon(Icons.favorite)
]),
),
Divider(color: Colors.black),
Row(children: detailRow),
]),
),
),
),
);
}
}
And instead of saving all the variables you had in app we can now make them into a list of CarDetails where each element contains the strings.
// some other widget
...
List<CarDetails> carDetails = [
CarDetails(
title: "2 hrs ago",
diffNumber: "+/ TRACK",
diffPercent: "% to DBJ",
),
CarDetails(
title: "CHEVEROLET",
diffNumber: "-2706",
diffPercent: "42.2%",
colorIndicator: Colors.red,
),
CarDetails(
title: "BUICK",
diffNumber: "+300",
diffPercent: "50%",
colorIndicator: Colors.green,
),
CarDetails(
title: "GMC",
diffNumber: "-712",
diffPercent: "52.1%",
colorIndicator: Colors.black26,
),
];
#override
Widget build(BuildContext context) {
return CarCard(
title: "US Daily Retail Delieveries by Brand",
carDetails: carDetails,
);
}
...
This can of course be abstracted even further with the groups of cards etc, etc. But I hope you get the idea.
This is an example of how you could do it, with that said I do not know what data you are intending to use and how you want to structure it. So consider this a starting point and take it from there. :)