How can I write a paragraph with bullet points using Flutter? - text

Using HTML I can add a bullet points to a paragraph like this:
<ul>
<li> example </li>
<li> example </li>
<li> example </li>
<ul>
How can I write bullet point form in Flutter?
new Text(''),

If you don't want to download another library (e.g. flutter_markdown), and one or more of your list items have lengthy text that spans several rows, I'd go with Raegtime's answer. However, since it assumes a string with line breaks, I want to make a version for a list with strings, which is a more common scenario. In the code below, Column makes the list items come on different rows, and Row makes the bullet points have empty space below themselves.
import 'package:flutter/material.dart';
class UnorderedList extends StatelessWidget {
UnorderedList(this.texts);
final List<String> texts;
#override
Widget build(BuildContext context) {
var widgetList = <Widget>[];
for (var text in texts) {
// Add list item
widgetList.add(UnorderedListItem(text));
// Add space between items
widgetList.add(SizedBox(height: 5.0));
}
return Column(children: widgetList);
}
}
class UnorderedListItem extends StatelessWidget {
UnorderedListItem(this.text);
final String text;
#override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("• "),
Expanded(
child: Text(text),
),
],
);
}
}
We can use it as such:
UnorderedList([
"What conclusions can we draw from the implementation?",
"Are there any changes that need to be introduced permanently?"
])
And get the result:

Using markdown for this is overkill. Using • character is by far easier.
If you're too lazy to copy paste the character, here's a custom Text that does it for you:
class Bullet extends Text {
const Bullet(
String data, {
Key key,
TextStyle style,
TextAlign textAlign,
TextDirection textDirection,
Locale locale,
bool softWrap,
TextOverflow overflow,
double textScaleFactor,
int maxLines,
String semanticsLabel,
}) : super(
'• ${data}',
key: key,
style: style,
textAlign: textAlign,
textDirection: textDirection,
locale: locale,
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
semanticsLabel: semanticsLabel,
);
}

I tried using flutter_markdown and it seems to work. And of course you can change it to numbered/ordered or unordered list as you want.
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter/material.dart';
void main() => runApp(Demo());
class Demo extends StatelessWidget {
final testData = ["Example1", "Example2", "Example3", "Example100"];
#override
Widget build(BuildContext context) {
final _markDownData = testData.map((x) => "- $x\n").reduce((x, y) => "$x$y");
return MaterialApp(
home: Scaffold(
body: Container(
margin: EdgeInsets.all(40.0),
child: Markdown(data: _markDownData)),
));
}
}

I would better use utf-code. For list I think more comfortably will be something like:
class DottedText extends Text {
const DottedText(String data, {
Key key,
TextStyle style,
TextAlign textAlign,
TextDirection textDirection,
Locale locale,
bool softWrap,
TextOverflow overflow,
double textScaleFactor,
int maxLines,
String semanticsLabel,
}) : super(
'\u2022 $data',
key: key,
style: style,
textAlign: textAlign,
textDirection: textDirection,
locale: locale,
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
semanticsLabel: semanticsLabel,);
}

#Snurrig - Excellent answer. Works great! Thanks a lot!
Modified it to create an ordered/numbered list, as well.
See below:
class OrderedList extends StatelessWidget {
OrderedList(this.texts);
final List<dynamic> texts;
#override
Widget build(BuildContext context) {
var widgetList = <Widget>[];
int counter = 0;
for (var text in texts) {
// Add list item
counter++;
widgetList.add(OrderedListItem(counter, text));
// Add space between items
widgetList.add(SizedBox(height: 5.0));
}
return Column(children: widgetList);
}
}
class OrderedListItem extends StatelessWidget {
OrderedListItem(this.counter, this.text);
final int counter;
final String text;
#override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("$counter. "),
Expanded(
child: Text(text),
),
],
);
}
}

you can use like this:
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'\u2022',
style: TextStyle(
fontSize: 16,
height: 1.55,
),
),
SizedBox(
width: 5,
),
Expanded(
child: Container(
child: Text(
str,
textAlign: TextAlign.left,
softWrap: true,
style: TextStyle(
fontSize: 16,
color: Colors.black.withOpacity(0.6),
height: 1.55,
),
),
),
),
],
);

You can use LineSplitter, Row, Column, and the ASCII bullet point. All u need is a String with linebreaks.
String myStringWithLinebreaks = "Line 1\nLine 2\nLine 3";
Example in a ListTile
ListTile(
title: Text("Title Text"),
subtitle:
Column(
children: LineSplitter.split(myStringWithLinebreaks).map((o) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("• "),
Expanded(
child: Text(o),
)
],
);
}).toList())),

If you do not require markdowns everywhere, and just want to use them in one or two places, then adding a package or writing that much code for it is not a suitable idea.
You can copy the DOT from websites like emojipedia and paste it in front of your text.
here is an example:
Text("⚈ Provide, operate, and maintain our website"),

This will add bullet. Use it in a row with text.
Container(width: 10, height: 10, decoration:
BoxDecoration(shape: BoxShape.circle, color: Colors.black),),

Row(
children: [
_buildBullet(),
const SizedBox(width: 5),
_buildText(),
],
),
SizedBox _buildBullet() {
return SizedBox(
height: 7,
width: 7,
child: TextButton(
style: TextButton.styleFrom(
backgroundColor: Color(0xFFF8B407),
shape: const CircleBorder(),
),
child: const Text(''),
onPressed: () {},
),
);
}
Text _buildText() {
return const Text(
'Lorem Ipsum is simply dummy text of the printing and typesetting',
style: TextStyle(fontSize: 24, color: Colors.white),
);
}

Related

How to split a user inputted string with a comma

so my teammate asked me to fix something. But the problem is I don't have any idea what to do.
He asked me to divide the address output with commas -
Ex. Barangay, House Number, Municipality, Street.
But I don't know how to seperate the contents of a string.
The address string contains all the user input data.
This is the code:
Widget buildUserAddress() {
return Container(
width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.only(top: 10, bottom: 20),
child: Text(
address,
maxLines: 3,
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 15,
),
),
);
}
below the complete Widget class. As you can see:
I split the string and I got a List
I put all in column (be free to change how to display)
For each element in the list, make a Text widget with label the address element
toList() -> list of Text widgets containing each one a part of address
... -> spread operator in order to display each element of the widget list
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return buildUserAddress();
}
Widget buildUserAddress() {
var address = "Barangay, House Number, Municipality, Street";
List<String> addressList = address.split(',');
return Container(
width: 500,
padding: const EdgeInsets.only(top: 10, bottom: 20),
child: Column(children: [
...addressList
.map(
(addressElement) => Text(
addressElement,
maxLines: 3,
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 15,
),
),
)
.toList(),
]),
);
}
}
List<String> components = address.split(',');
This will split the address and you will get the component list.
And use splitted components to build UI like this.
ListView.builder(
itemCount: components.length,
itemBuilder: (context, index) => Text(components[index])
)

showModalBottomSheet Performance issue take about 3 seconds to appear

I have several modalBottomSheet in my app to show, most of them have simple widget tree except only one that has 10 DropDownBottom widgets in it
Each one of them load about 200 items, each item is a widget consist of two main widgets a text and an image
when I Press
onPressed: () {
showModalBottomSheet(context: context, builder: picksWidget, isScrollControlled: true);
}
It take about 3 seconds to open load the modalBottomSheet and it just appear into the emulator without the sliding up animation, other modalBottomSheets in the app load perfectly fine, here is an example of the code I use.
import 'package:flutter/material.dart';
import 'package:myapp/data/picked.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:myapp/data/picks.dart';
import 'package:myapp/data/Icons.dart';
Widget buildPickerBottomSheet(BuildContext context) {
return Wrap(children: <Widget>[
PickerList(),
]);
}
class PickerList extends StatelessWidget {
const PickerList({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
padding: EdgeInsets.all(20),
child: Row(
children: <Widget>[
AutoSizeText(
'Pick ',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
textAlign: TextAlign.right,
maxLines: 1,
),
],
),
),
SizedBox(
height: 5,
),
PickerRow(
typeOne: 'vr',
typeTwo: 'er',
type: 'r',
),
PickerRow(
typeOne: 'vq',
typeTwo: 'eq',
type: 'q',
),
PickerRow(
typeOne: 'vw',
typeTwo: 'ew',
type: 'w',
),
PickerRow(
typeOne: 'vz',
typeTwo: 'ez',
type: 'z',
),
PickerRow(
typeOne: 'vy',
typeTwo: 'ey',
type: 'y',
),
SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RaisedButton(
child: Text('Add'),
onPressed: () async {
print('added');
},
),
RaisedButton(
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
)
],
),
SizedBox(
height: 50,
)
],
);
}
}
class PickerRow extends StatelessWidget {
final String typeOne;
final String typeTwo;
final String type;
PickerRow({#required this.typeOne, #required this.typeTwo, #required this.type});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 20, right: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
DropDownMenu(
pickType: typeOne,
),
Container(
width: 2,
height: 30,
color: Colors.blue,
),
Container(
height: 30,
width: 30,
child: Image(
image: AssetImage(AppIcons.types[type]),
),
),
Container(
width: 2,
height: 30,
color: Colors.red,
),
DropDownMenu(
pickType: typeTwo,
),
],
),
);
}
}
class DropDownMenu extends StatefulWidget {
//which position will this pick for
final String pickType;
DropDownMenu({#required this.pickType});
#override
_DropDownMenuState createState() => _DropDownMenuState();
}
class _DropDownMenuState extends State<DropDownMenu> {
//get a list of the picks to display in the drop down
static List<DropdownMenuItem> getDropDownItems() {
List<DropdownMenuItem<String>> dropDownItems = [];
for (int i = 0; i < Picks.picksNames.length; i++) {
String pick = Picks.picksNames[i];
var newItem = DropdownMenuItem(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
CircleAvatar(
backgroundImage: AssetImage(AppIcons.picks[pick]),
radius: 15,
),
SizedBox(
width: 4,
),
AutoSizeText(
pick,
maxLines: 1,
),
],
),
value: pick,
);
dropDownItems.add(newItem);
}
return dropDownItems;
}
var items = getDropDownItems();
#override
Widget build(BuildContext context) {
String chosenItem = Picked.selection[widget.pickType];
return DropdownButton<String>(
value: chosenItem,
items: items,
onChanged: (value) {
setState(() {
chosenItem = value;
});
Picked.selection[widget.pickType] = value;
},
);
}
}
I am new to development in general so I appreciate if there is any resources on how to measure and improve performance on flutter apps.
Thanks.
It seems like your bottomModalSheet is too heavy. It loads only after the children have completed building.
Resize the images that you use inside the bottomModalSheet.
Usually the debug app is very slow compared to the release app.
Generate the apk of the app: flutter build apk --split-per-abi.
Install the generated apk build/app/outputs/apk/release/app-armeabi-v7a-release.apk and see if the problem still persists.
You should avoid rendering 2000 widgets at once.
First of all look at ListView widget and it's builder(). Try to refactor your code and put all your items inside this ListView. It will increase your performance a lot due to the reason that ListView.builder() allows you not to store all your widgets in memory all the time.
https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html

How to create a simple google maps address search with autocomplete in flutter and get latitude and longitude?

I'm new at Flutter and I'm trying to build a simple google maps app. I've already implemented google maps to the app and it is running perfect.
But now I want to add google maps autocomplete and I can't find a simple tutorial or example that is focused on it.
I have a TextField and I want to show places and addresses below it according to what the user types.
After showing the results, I need to get its latitude and longitude to mark on the map. The code below represents my BottomSheet, that contains my TexField and need to implement some list below it after some written text.
void _settingModalBottomSheet(context) {
double statusBarHeight = MediaQuery.of(context).padding.top;
showModalBottomSheet(
context: context,
builder: (builder) {
return Container(
padding: EdgeInsets.only(top: statusBarHeight),
color: Colors.transparent,
child: Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
color: Colors.blueAccent,
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(10.0), topRight: const Radius.circular(10.0))),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0),
child: Container(
height: 50.0,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: Colors.white
),
child: TextField(
textInputAction: TextInputAction.search,
decoration: InputDecoration(
hintText: "Para onde vamos?",
border: InputBorder.none,
contentPadding: EdgeInsets.only(left: 15.0, top: 15.0),
suffixIcon: IconButton(
icon: Icon(Icons.search),
onPressed: searchAndNavigate,
iconSize: 30.0,
)
),
onChanged: (val) {
setState(() {
searchAddr = val;
}
);
},
onSubmitted: (term) {
searchAndNavigate();
},
),
),
),
],
)
),
);
}
);
}
You can use flutter_google_places plugin which shows the places in the autocomplete list as you type it and also returns lat and long of the place/address selected.
===== Working code =======
Add flutter_google_places plugin and import it in your dart file.
Add geo_coder plugin and import it in same dart file. (Required to access geocoding services)
Generate google api key for your project.
main.dart:
void main() => runApp(MyApp());
const kGoogleApiKey = "Api_key";
GoogleMapsPlaces _places = GoogleMapsPlaces(apiKey: kGoogleApiKey);
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: demo(),
),
);
}
}
class demo extends StatefulWidget {
#override
demoState createState() => new demoState();
}
class demoState extends State<demo> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: RaisedButton(
onPressed: () async {
// show input autocomplete with selected mode
// then get the Prediction selected
Prediction p = await PlacesAutocomplete.show(
context: context, apiKey: kGoogleApiKey);
displayPrediction(p);
},
child: Text('Find address'),
)
)
);
}
Future<Null> displayPrediction(Prediction p) async {
if (p != null) {
PlacesDetailsResponse detail =
await _places.getDetailsByPlaceId(p.placeId);
var placeId = p.placeId;
double lat = detail.result.geometry.location.lat;
double lng = detail.result.geometry.location.lng;
var address = await Geocoder.local.findAddressesFromQuery(p.description);
print(lat);
print(lng);
}
}
}
Result:
When you tap on Find Address button, it opens new screen with built-in search app bar in which you can type address / place you are looking for which shows corresponding results in autocomplete list and prints lat and long of the place you selected.
lat: 52.3679843
lng: 4.9035614

FittedBox and Text whitespace issue

I'm trying to make all Text(length varies) in a fixed width box to fit and I want them to look at the same size as the longest word's size when FittedBox applied. So What I'm doing to achieve that is filling the rest of the word with empty space to match the length to the longest word. But that doesn't quite work as you see in the following image:
Here is the 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,
),
routes: {
'/': (context) => HomePage(),
},
);
}
}
class HomePage extends StatelessWidget {
final String padding = " " * 5;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Container(
color: Colors.red,
height: 100.0,
width: 100.0,
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
"${padding}Demo$padding",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.black),
),
),
),
Icon(Icons.mic, size: 24.0),
],
),
),
),
);
}
}
Do you have any idea what is the issue here?
It's really strange, but it seems to work correctly if you remove alignment or set it to start. (What cause this bug - I still don't know)
child: Text('${padding}Demo$padding',
style: TextStyle(color: Colors.black, decoration: TextDecoration.underline),)
I've added decoration to see these spaces

Flutter dropdown text field

I am still new to Flutter. Is there an example of a material dropdown list text field? I saw the example on Material Text Field but I didn't find anywhere in the documentation on how to implement this. Thanks for your help on this.
UPDATED :
Text form field with a dropdown
var _currencies = [
"Food",
"Transport",
"Personal",
"Shopping",
"Medical",
"Rent",
"Movie",
"Salary"
];
FormField<String>(
builder: (FormFieldState<String> state) {
return InputDecorator(
decoration: InputDecoration(
labelStyle: textStyle,
errorStyle: TextStyle(color: Colors.redAccent, fontSize: 16.0),
hintText: 'Please select expense',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(5.0))),
isEmpty: _currentSelectedValue == '',
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: _currentSelectedValue,
isDense: true,
onChanged: (String newValue) {
setState(() {
_currentSelectedValue = newValue;
state.didChange(newValue);
});
},
items: _currencies.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
);
},
)
Hope this helps!
You want the DropdownButton or DropdownButtonFormField
https://api.flutter.dev/flutter/material/DropdownButton-class.html
and the DropdownMenuItem
https://api.flutter.dev/flutter/material/DropdownMenuItem-class.html
return DropdownButtonFormField(
items: categories.map((String category) {
return new DropdownMenuItem(
value: category,
child: Row(
children: <Widget>[
Icon(Icons.star),
Text(category),
],
)
);
}).toList(),
onChanged: (newValue) {
// do other stuff with _category
setState(() => _category = newValue);
},
value: _category,
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(10, 20, 10, 20),
filled: true,
fillColor: Colors.grey[200],
hintText: Localization.of(context).category,
errorText: errorSnapshot.data == 0 ? Localization.of(context).categoryEmpty : null),
);
Other answers have fully described what you need, but here is an example that puts it all together, this is a reusable dropdown textfield widget that allows you to specify a list of options of any type (without losing dart's beautiful type system).
class AppDropdownInput<T> extends StatelessWidget {
final String hintText;
final List<T> options;
final T value;
final String Function(T) getLabel;
final void Function(T) onChanged;
AppDropdownInput({
this.hintText = 'Please select an Option',
this.options = const [],
this.getLabel,
this.value,
this.onChanged,
});
#override
Widget build(BuildContext context) {
return FormField<T>(
builder: (FormFieldState<T> state) {
return InputDecorator(
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
horizontal: 20.0, vertical: 15.0),
labelText: hintText,
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(5.0)),
),
isEmpty: value == null || value == '',
child: DropdownButtonHideUnderline(
child: DropdownButton<T>(
value: value,
isDense: true,
onChanged: onChanged,
items: options.map((T value) {
return DropdownMenuItem<T>(
value: value,
child: Text(getLabel(value)),
);
}).toList(),
),
),
);
},
);
}
}
And you may use it like this:
AppDropdownInput(
hintText: "Gender",
options: ["Male", "Female"],
value: gender,
onChanged: (String value) {
setState(() {
gender = value;
// state.didChange(newValue);
});
},
getLabel: (String value) => value,
)
Following Jeff Frazier's answer, You can have more customization by using DropdownButton2 or DropdownButtonFormField2 from DropdownButton2 package. It's based on Flutter's core DropdownButton with more options you can customize to your needs.
This answer provide a example using a DropdownButtonFormField a convenience widget that wraps a DropdownButton widget in a FormField.
Ideal if you are using a Material FormField
'Dropdown' may not be the correct word that you are using to describe the design of text field referred in your material design example.
Here is how to implement it in Flutter:
import 'package:flutter/material.dart';
void main() {
runApp(TextFieldExample());
}
class TextFieldExample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Text Field Example',
home: HomePage(),
theme: ThemeData(
primaryColor: Colors.deepPurple,
accentColor: Colors.white,
),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Text Field Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
//Material example
TextField(
decoration: InputDecoration(
filled: true,
hintText: 'Enter text',
labelText: 'Default text field'),
controller: new TextEditingController(),
),
SizedBox(
height: 16.0,
),
//Alternate
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter text',
labelText: 'Text field alternate'),
controller: new TextEditingController(),
),
],
),
),
);
}
}
This sample app contains two different examples of text field design that shrink and expand the associated label.
Gif of sample app - click here

Resources