I create apps that user can load csv file and i want csv data put into map so that i can make delete function or anything else.... Can somebody help me because i been searching for this it show how to convert csv using loadasset bundle i want to do it with pick file from phone storage
example: convert data from a csv to a dynamic List (Flutter)
I/flutter ( 4028): [[No, Name, Code], [1, Ali, A123], [2, Abu, B456], [3, Amir, C789], [4, Safe, D098], [5, Alif, E765 ]]
here my code
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("CSV DATA"),
backgroundColor: Colors.redAccent[400],
),
body: Container(
child: SingleChildScrollView(
child: Column(children: <Widget>[
FutureBuilder(
future: loadingCsvData(path),
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
print(snapshot.data.toString());
return snapshot.hasData
? ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: snapshot.data.map((data) {
return Visibility(
visible: visibilityController,
child: //Your card
Card(
child: GestureDetector(
onTap: () {
withInputField(context);
controller.text = data[1];
_controller.text = data[2];
setState(() {});
},
child: ListTile(
title: Text(
data[1].toString(),
style: TextStyle(
fontWeight: FontWeight.bold),
),
subtitle: Text(data[2].toString()),
isThreeLine: true,
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {},
),
),
),
));
}).toList(),
)
: Center(
child: CircularProgressIndicator(),
);
},
),
]),
)),
This my function for file picker
Future<List<List<dynamic>>> loadingCsvData(String path) async {
final csvFile = new File(path).openRead();
return await csvFile
.transform(utf8.decoder)
.transform(
CsvToListConverter(),
)
.toList();
}
EDIT
my fetch data
FutureBuilder(
future: loadingCsvData(path),
builder: (context,
AsyncSnapshot<Map<int, Map<String, dynamic>>> snapshot) {
print(snapshot.data.toString());
return snapshot.hasData
? ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: [
Card(
child: ListTile(
title: Text(userList.toString()),
),
)
],
)
: Center(
child: CircularProgressIndicator(),
);
},
),
is it right to do like this
I would create a model for the object you want to read.
Whithin the model I would prepare some functions to convert a single of your Row in a Map<String, dynamic> and to convert a List of Rows in a Map<int, Map<String, dynamic>>.
Here's an example:
//I suppose your single row element to be an User
class User {
User({
No,
Name,
Code,
}) {
this.No = No;
this.Name = Name;
this.Code = Code;
}
//Parameters
int? No;
String? Name;
String? Code;
// A map with an String key and dynamic value
static Map<String, dynamic> userToMap(List<dynamic> row) {
return {
if (row[0] != null) 'No': row[0] as int,
if (row[1] != null) 'Name': row[1] as String,
if (row[2] != null) 'Code': row[2] as String
};
}
// A map with an int key and Map<String, dynamic> value
static Map<int, Map<String, dynamic>> userListToMap(
List<List<dynamic>> userList) {
userList.removeAt(0); //If you want to remove the first row containing the headers
return userList.map((user) => userToMap(user)).toList().asMap();
}
}
Then you could take the List<List<dynamic>> returned from your method loadingCsvData and call userListToMap.
UPDATE:
After writing my answer I noticed that you don't actually need the class I built, you could take the functions userToMap and userListToMap alone and it should work.
EDIT:
You could change your loadingCsvData to look like this:
Future<Map<int, Map<String, dynamic>>> loadingCsvData(String path) async {
final csvFile = new File(path).openRead();
return userListToMap(await csvFile
.transform(utf8.decoder)
.transform(
CsvToListConverter(),
).toList());
}
Related
I'm trying to display stored bookmarks in a grouped listview but getting this error:
Class 'Bookmark' has no instance method '[]'.
Receiver: Instance of 'Bookmark'
Tried calling: []("surah_name")
The bookmarks are stored in shared preferences so we need to to fetch them on page load and display them in a listview grouped by surah name. I'm able to do it with a static list but unable to do it with a dynamic list and a futureBuilder.
The data that's coming from the shared preferences looks like this:
[{"surah_no":2,"verse_no":2,"surah_name":"Al-Baqarah","favorite":true}, {"surah_no":2,"verse_no":1,"surah_name":"Al-Baqarah","favorite":true}, {"surah_no":2,"verse_no":2,"surah_name":"Al-Baqarah","favorite":true}, {"surah_no":2,"verse_no":3,"surah_name":"Al-Baqarah","favorite":true}, {"surah_no":4,"verse_no":1,"surah_name":"An-Nisa'","favorite":true}, {"surah_no":8,"verse_no":1,"surah_name":"Al-Anfal","favorite":true}, {"surah_no":6,"verse_no":1,"surah_name":"Al-'An'am","favorite":true},...]
The code right now is :
class Bookmark {
final int surah_no, verse_no;
final String surah_name;
bool favorite;
Bookmark({this.surah_no, this.verse_no, this.surah_name, this.favorite});
Bookmark.fromJson(Map<String, dynamic> jsonData)
:
surah_no = jsonData['surah_no'],
verse_no = jsonData['verse_no'],
surah_name= jsonData['surah_name'],
favorite = jsonData['favorite'];
Map<String, dynamic> toJson() =>
{
'surah_no': surah_no,
'verse_no': verse_no,
'surah_name': surah_name,
'favorite': favorite,
};
}
class _BookmarkScreenState extends State<BookmarkScreen> {
List bookmarks=[];
List<Bookmark> bks = <Bookmark>[];
Bookmark b = new Bookmark();
#override
void initState() {
super.initState();
_loadData();
setState(() {
});
}
Future<List<Bookmark>> _loadData() async {
bookmarks.clear();
SharedPreferences pref = await SharedPreferences.getInstance();
List<String> lb = await pref.getStringList(('bookmarks_key'));
print(lb);
lb.forEach((element) {bookmarks.add(element);});
bks = bookmarks.map((s) => Bookmark()).toList();
print(bks);
return bks;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Bookmark Screen")),
body: Container(
child: FutureBuilder(
future: _loadData(),
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
return GroupedListView<dynamic, String>(
elements: bks,
groupBy: (element) => element['surah_name'],
groupComparator: (value1, value2) => value2.compareTo(value1),
itemComparator: (item1, item2) =>
item1['verse_no'].compareTo(item2['verse_no']),
order: GroupedListOrder.DESC,
useStickyGroupSeparators: true,
groupSeparatorBuilder: (String value) => Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
value,
textAlign: TextAlign.center,
style:
TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
)),
itemBuilder: ((c, element) {
return Card(
elevation: 8.0,
margin: new EdgeInsets.symmetric(
horizontal: 10.0, vertical: 6.0),
child: Container(
child: ListTile(
contentPadding: EdgeInsets.symmetric(
horizontal: 20.0, vertical: 10.0),
title: Text(element['verse_no'].toString()),
),
),
);
}));}})));
}
}
Please help me solve this error. Thank you
If there is a better way to store the list I have made, please do help.
The objective is to display the information in each List Tile in the format of :
Code: LE-0000000002,
Description: test_01,
Organisation Unit: 01_01_04_01_SA - Shah Alam,
Date Reported: Date Reported: 18/09/2020,
This is the code to obtain and store the list :
onTap: () async {
var newMessage = await (ReadCache.getString(key: 'cache1'));
var response = await http.get(
Uri.parse(
'http://192.168.1.8:8080/HongLeong/MENU_REQUEST.do?_dc=1658076505340&reset_context=1&table_id=25510&id_MenuAction=3&path=%3Cp%20class%3D%22x-window-header-path%22%3ELoss%20Event%3C%2Fp%3E&ViewType=MENU_REQUEST&gui_open_popup=1&id_Window=17&activeWindowId=mw_17&noOrigUserDate=true&LocalDate=20220718&LocalTime=00482500&TimeZone=Asia%2FShanghai&UserDate=0&UserTime=0&server_name=OPRISK_DATACOLLECTOR&key_id_list=&cell_context_id=0&id_Desktop=100252&operation_key=1000184&operation_sub_num=-1&is_json=1&is_popup=0&is_search_window=0&ccsfw_conf_by_user=0&is_batch=0&previousToken=1658069547560&historyToken=1658076505339&historyUrl=1'),
headers: {HttpHeaders.cookieHeader: newMessage},
);
LossEventResponseModel lossEventResponseModel =
LossEventResponseModel.fromJson(jsonDecode(response.body));
final listNode = lossEventResponseModel.response.genericListAnswer.listNode;
List<Map<String, dynamic>> incidentList = [
for (final json in listNode.map((x) => x.toJson()))
{
'Code': json['field'][0]['field_value'],
'Description': json['field'][1]['field_value'],
'Organisation Unit': json['field'][46]['field_value'],
'Date Reported': json['field'][18]['field_value'],
}
];
final List<String>values = [];
for(final item in incidentList){
values.addAll(item.values.map((e) => e.toString()));
}
await WriteCache.setListString(key: 'cache4', value: values);
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => LossEvent()));
}
This is the code to read and display the list :
body: Column(
children: [
Expanded(
child: FutureBuilder(
future: ReadCache.getStringList(key: 'cache4'),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(5.0),
child: ListTile(
title: Text(
snapshot.data[index],
style: GoogleFonts.poppins(
textStyle : const TextStyle(
fontWeight: FontWeight.normal,
fontSize: 20,
),
)
),
tileColor: Colors.blueGrey[200],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
);
});
} else {
return const Text("No Data");
}
},
),
),
],
),
Instead it is displaying everything in its own list tile separately which isn't the objective here and this is what it is displaying now :
The problem could be the addAll here:
final List<String>values = [];
for(final item in incidentList){
values.addAll(item.values.map((e) => e.toString()));
}
Maybe you want something this instead:
final List<String> values = [];
for(final item in incidentList){
values.add(item.values.map((e) => e.toString()).join("\n"));
}
After migrating to null safety I'm getting an error on ListView as "The argument type 'Object?' can't be assigned to the parameter type 'List'."
I'm getting error on return ListView(children: snapshot.data,);
Can anyone help me to fix this error and build a ListView for activityfeeditem in my app?
Here is my code for activity_feed.dart,
class ActivityFeed extends StatefulWidget {
#override
_ActivityFeedState createState() => _ActivityFeedState();
}
class _ActivityFeedState extends State<ActivityFeed> {
getActivityFeed() async {
QuerySnapshot snapshot = await activityFeedRef
.doc(currentUser!.id)
.collection('feedItems')
.orderBy('timestamp', descending: true)
.limit(50)
.get();
List<ActivityFeedItem> feedItems = [];
snapshot.docs.forEach((doc) {
feedItems.add(ActivityFeedItem.fromDocument(doc));
print('Activity Feed Item: ${doc.data}');
});
return feedItems;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.deepPurple[50],
appBar: header(context, titleText: "Activity Feed"),
body: Container(
child: FutureBuilder(
future: getActivityFeed(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
return ListView(
children: snapshot.data,
// Here I'm getting error on `snapshot.data`
);
},
),
),
);
}
}
Widget? mediaPreview;
String? activityItemText;
class ActivityFeedItem extends StatelessWidget {
final String? username;
final String? userId;
final String? type; // 'like', 'follow', 'comment'
final String? mediaUrl;
final String? postId;
final String? userProfileImg;
final String? commentData;
final Timestamp? timestamp;
ActivityFeedItem({
this.username,
this.userId,
this.type,
this.mediaUrl,
this.postId,
this.userProfileImg,
this.commentData,
this.timestamp,
});
factory ActivityFeedItem.fromDocument(DocumentSnapshot doc) {
return ActivityFeedItem(
username: doc['username'],
userId: doc['userId'],
type: doc['type'],
postId: doc['postId'],
userProfileImg: doc['userProfileImg'],
commentData: doc['commentData'],
timestamp: doc['timestamp'],
mediaUrl: doc['mediaUrl'],
);
}
showPost(context) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PostScreen(postId: postId, userId: userId)));
}
configureMediaPreview(context) {
if (type == "like" || type == 'comment') {
mediaPreview = GestureDetector(
onTap: () => showPost(context),
child: Container(
height: 50.0,
width: 50.0,
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: CachedNetworkImageProvider(mediaUrl!),
),
),
)),
),
);
} else {
mediaPreview = Text('');
}
if (type == 'like') {
activityItemText = "liked your post";
} else if (type == 'follow') {
activityItemText = "is following you";
} else if (type == 'comment') {
activityItemText = 'replied: $commentData';
} else {
activityItemText = "Error: Unknown type '$type'";
}
}
#override
Widget build(BuildContext context) {
configureMediaPreview(context);
return Padding(
padding: EdgeInsets.only(bottom: 2.0),
child: Container(
color: Colors.white54,
child: ListTile(
title: GestureDetector(
onTap: () => showProfile(context, profileId: userId),
child: RichText(
overflow: TextOverflow.ellipsis,
text: TextSpan(
style: TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: [
TextSpan(
text: username,
style: TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(
text: ' $activityItemText',
),
]),
),
),
leading: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(userProfileImg!),
),
subtitle: Text(
timeago.format(timestamp!.toDate()),
overflow: TextOverflow.ellipsis,
),
trailing: mediaPreview,
),
),
);
}
}
showProfile(BuildContext context, {String? profileId}) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Profile(
profileId: profileId,
),
),
);
}
I have tried many ways but I counldn't figure out how I can fix this
New code for list
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.deepPurple[50],
appBar: header(context, titleText: "Activity Feed"),
body: Container(
child: StreamBuilder<QuerySnapshot>(
stream: activityFeedRef
.doc(currentUser!.id)
.collection('feedItems')
.orderBy('timestamp', descending: true)
.limit(50)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: circularProgress(),
);
} else
return ListView(
children: snapshot.data!.docs.map((doc) {
return Card(
child: ListTile(
title: GestureDetector(
onTap: () =>
showProfile(context, profileId: doc['userId']),
child: RichText(
overflow: TextOverflow.ellipsis,
text: TextSpan(
style: TextStyle(
fontSize: 14.0,
color: Colors.black,
),
children: [
TextSpan(
text: doc['username'],
style: TextStyle(
fontWeight: FontWeight.bold),
),
TextSpan(
text: ' $activityItemText',
),
]),
),
),
leading: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
doc['userProfileImg']!),
),
subtitle: Text(
timeago.format(doc['timestamp']!.toDate()),
overflow: TextOverflow.ellipsis,
),
trailing: mediaPreview,
),
);
}).toList(),
);
}),
));
}
Chage your getActivityFeed
Future<List<ActivityFeedItem>> getActivityFeed() async {
try{
QuerySnapshot snapshot = await activityFeedRef
.doc(currentUser!.id)
.collection('feedItems')
.orderBy('timestamp', descending: true)
.limit(50)
.get();
List<ActivityFeedItem> feedItems = [];
snapshot.docs.forEach((doc) {
feedItems.add(ActivityFeedItem.fromDocument(doc));
print('Activity Feed Item: ${doc.data}');
});
return feedItems;
}
catch (error) {
print(error);
return <ActivityFeedItem>[];
}}
change you FutureBuilder as follows
FutureBuilder<List<ActivityFeedItem>>(
future: getActivityFeed(),
builder: (BuildContextcontext, AsyncSnapshot<List<ActivityFeedItem>> snapshot) {
if (snapshot.hasError){
return Center(child: Text("You have an error in loading
data"));
}
if (snapshot.hasData) {
return ListView(
children: snapshot.data!,
);
}
return CirclularProgressIndicator();
You can also use as.
ListView(
children: object as List<Widget>,
)
I see that you are using a StreamBuilder instead of FutureBuilder, but for what its worth, I believe I have found a solution to the original FutureBuilder problem.
First of all: Using the following print statements, you can troubleshoot the issue better, I found that I was Querying for paths that didnt exist with combinations of wrong .doc(userId) and .doc(ownerId) in posts.dart so the deserialization process wasn't working correctly for me when switching between users during debugging (used a provider package to remedy this eventually) but the below print statements did help me identify some issues that I had (that may or may not have contributed to the problem for you, but worth the look).
getActivityFeed() async {
QuerySnapshot snapshot = await FirebaseFirestore.instance
.collection('feed')
.doc(_auth.currentUser!.uid)
.collection('feedItems')
.orderBy('timestamp', descending: true)
.limit(50)
.get();
List<ActivityFeedItem> feedItems = [];
snapshot.docs.forEach((doc) {
feedItems.add(ActivityFeedItem.fromDocument(doc));
print('Activity Feed Item: ${doc.id}');
print('Activity Feed Item: ${doc.data()}');
});
// return feedItems;
return snapshot.docs;
Then I found that the deserialization process wasn't working correctly due to the difference between 'likes' and 'comments', due to the 'likes' having 7 inputs and 'comments' having 8 inputs. Comments have the extra 'commentData' input which I set up manually for the 'likes' in the addLikeToActivityFeed() part, and set it to an empty string as such:
'commentData': '',
Finally, I added a Dynamic type to the FutureBuilder to get rid of the => argument type 'Object?' can't be assigned to the parameter type 'List' error...
Container(
child: FutureBuilder<dynamic>(
future: getActivityFeed(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
return ListView(
children: snapshot.data,
);
},
),
),
I have the code which only deals with searching words from cloud_firestore which is a firebase service. Searching is done fine and everything is working fine but i would love to convert my firebase DocumentSnapshot to a custom model. I don't wan;t it to be showing instace of 'DocumentSnapShot' of which i don't wan't. I wan't it to be showing at least instance of 'WordsSearch' when i print(data):
Full search code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:kopala_dictionary/models/words.dart';
import 'package:kopala_dictionary/screens/author/word_details.dart';
import 'package:provider/provider.dart';
class CloudFirestoreSearch extends StatefulWidget {
#override
_CloudFirestoreSearchState createState() => _CloudFirestoreSearchState();
}
class _CloudFirestoreSearchState extends State<CloudFirestoreSearch> {
String name = "";
//words list from snappshots
List<Words> _wordsFromSnapShots(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return Words(
word: doc.data['word'],
englishTranslation: doc.data['english_translation'],
bembaTranslation: doc.data['bemba_translation'],
);
}).toList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop();
},
),
title: Card(
child: TextField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search), hintText: 'Search...'),
onChanged: (val) {
setState(() {
name = val;
});
},
),
),
),
body: StreamBuilder<QuerySnapshot>(
stream: (name != "" && name != null)
? Firestore.instance
.collection('words')
.where("word", isGreaterThanOrEqualTo: name)
.snapshots()
: Firestore.instance.collection("words").snapshots(),
builder: (context, snapshot) {
return (snapshot.connectionState == ConnectionState.waiting)
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot data = snapshot.data.documents[index];
print(data);
return Card(
child: Column(
children: <Widget>[
SizedBox(
width: 25,
),
ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
WordsDetails(word: data)),
);
},
leading: Text(
data['word'],
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 20,
),
),
),
],
),
);
},
);
},
),
);
}
}
And this is a custom WordsSearch class:
class WordsSearch {
final String word;
final String englishTranslation;
final String bembaTranslation;
final DateTime date_posted;
Words(
{this.word,
this.englishTranslation,
this.bembaTranslation,
this.date_posted});
}
You can do this using a .fromFirestore method in your model object.
factory Words.fromFirestore(DocumentSnapshot doc) {
Map data = doc.data();
// you likely need to convert the date_posted field. something like this
Timestamp datePostedStamp = data['date_posted'];
var datePosted = new DateTime.fromMillisecondsSinceEpoch(datePostedStamp.millisecondsSinceEpoch);
return Words(
word: data['word'] ?? '',
englishTranslation: data[''] ?? '',
bembaTranslation: data['bembaTranslation'] ?? '',
date_posted: datePosted
);
}
Elsewhere...
List<Words> _wordsFromSnapShots(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return Words.fromFirestore(doc);
}).toList();
}
Remember to convert the date posted before sending into firebase. Here's how I've done it.
Timestamp.fromMillisecondsSinceEpoch(DateTime.now().millisecondsSinceEpoch)
i want to build Dropdown list from Future,here is my function for simple list view which is working,,but how to populate drop down list from it, i am really confuse about this list map etc in flutter coming from php background,
child: FutureBuilder(
future:userApi.getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot){
if(snapshot.data == null){
return Container(
child: Center(
child: Text("Loading...")
)
);
} else {
return Container(
child: DropdownButton(
items: snapshot.data.map((item) {
return DropdownMenuItem(child: Text(item.title));
}).toList(),
onChanged: (value){},
)
);
}
},
),
class UserApi{
Future<List<User>>getUsers() async {
var data = await http.get("https://jsonplaceholder.typicode.com/albums/");
var jsonData = json.decode(data.body);
List<User> users = [];
for(var u in jsonData){
User user = User(u["id"], u["title"]);
users.add(user);
}
return users;
}
class User {
final int id;
final String title;
User(this.id,this.title);
}
Ok I just read the comment left above if your problem is getting your data then this might not help you but if snapshot has data this will work
//initialize this outside your build method
String dropDownValue;
FutureBuilder(
future:userApi.getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.hasData
? Container(
child: DropdownButton<String>(
hint: Text(dropDownValue ?? 'Make a selection'),
items: snapshot.data.map<DropdownMenuItem<String>>((item) {
return DropdownMenuItem<String>(
value: item.title,
child: Text(item.title),
);
}).toList(),
onChanged: (value) {
setState(() {
dropDownValue = value;
print(value);
});
},
),
)
: Container(
child: Center(
child: Text('Loading...'),
),
);
},
),
you can call the map function on any list converting the elements of the list.
return DropdownButton(
items: snapshot.data.map((item) {
return DropdownMenuItem(child: Text(item.title));
}).toList(),
onChanged: (value){},
);
if you can see here we're converting the list snapshot.data to another list of DropdownMenuItem type by calling map on snapshot.data the map function takes another function that's being called on every element of snapshot.data , map returns an Iterable of type DropdownMenuItem (the returned type of the function being called on every element) and we convert the Iterable to a list by calling toList() on it
I wish that is explanatory enough, the map function is very useful.