I am using Firestore as a database for a Chat in one part of the application that I am building. I have a collection on my Firestore that looks like this:
messages(collection)
userId
userId-otherUserId(sub-collection)
randomId
content : String,
timestamp : Date
...
and I would like to retrieve firstly all of the userIs-otherUserId in a ListView and then to retrieve the lastMessage by retrieving the last randomId(and in that randomId the last message is the last element with key 'content') and I would like it to look something like this.
Prototype
where the Loading represents last message but the name and profilePicture I can get from my API.
The pictures of my Firestore database:
First Image
Second Image
Third Image
Is there any chance that I could save the name and Profile picture on the database in the userId-otherUserId(collection)?
The code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dio/dio.dart';
import 'package:disby_app/const.dart';
import 'package:disby_app/data/request_model.dart';
import 'package:disby_app/global_widgets/loading_indicator.dart';
import 'package:disby_app/main_screens/main_chat_and_request_screens/chat.dart';
import 'package:disby_app/main_screens/main_chat_and_request_screens/profile_details_screen.dart';
import 'package:disby_app/public/keys.dart';
import 'package:disby_app/services/json_requests.dart';
import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../global_widgets/chat_and_requests_placeholder.dart';
class MainChatScreen extends StatefulWidget {
#override
_MainChatScreenState createState() => _MainChatScreenState();
}
class _MainChatScreenState extends State<MainChatScreen> {
bool _areThereChats = false;
bool _isLoading = true;
String groupChatId;
String userId = '1';
List<String> listOfLastMessages = [];
List<String> listOfIds = [];
List<ArrayOfRequestsModel> arrayOfRequests = [];
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
Future readLocal() async {
var prefs = await SharedPreferences.getInstance();
userId = prefs.getString(kUserId) ?? '';
print(userId);
setState(() {});
}
Future<void> _getRequests() async {
final prefs = await SharedPreferences.getInstance();
final userId = prefs.getString(kUserId);
try {
await Dio().post(baseUrl + acceptedUsers + "/" + userId).then((snapshot) {
print(snapshot);
final Map<String, dynamic> response = snapshot.data;
print(response);
if (response['response'] == 'success') {
List<dynamic> arrayFromServer = response['acceptedUsers'];
if (arrayFromServer.isNotEmpty) {
arrayFromServer.forEach((userData) {
arrayOfRequests.add(ArrayOfRequestsModel.fromJson(userData));
listOfIds.add(userData['userId']);
print(userData['userId']);
});
_areThereChats = true;
_isLoading = false;
getMessages(userId);
} else {
_areThereChats = false;
_isLoading = false;
}
setState(() {});
} else {
_areThereChats = false;
_isLoading = false;
setState(() {});
}
});
} catch (e) {
print(e);
}
}
getMessages(String userId) {
int i = 0;
print(listOfIds.length);
print(listOfIds);
listOfIds.forEach((element) {
Firestore.instance
.collection('messages')
.document('$userId')
.collection('$userId-$element')
.orderBy('timestamp', descending: true)
.limit(3)
.getDocuments()
.then((QuerySnapshot snapshot) {
print(snapshot.documents[0].data['content']);
snapshot.documents.forEach((f) {
print(f.data['content']);
listOfLastMessages.add(f.data['content']);
setState(() {});
i++;
});
if (i == listOfIds.length) {
setState(() {});
print(listOfLastMessages);
}
});
});
}
#override
void initState() {
readLocal();
_getRequests();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Container(
width: double.infinity,
height: double.infinity,
child: SingleChildScrollView(
// child: (userId != '1')
// ? StreamBuilder(
// stream: Firestore.instance
// .collection('messages')
// .document('5ef4d83175b07cf074525c08')
// .snapshots(),
// builder: (context, snapshot) {
// // if (snapshot.hasData) {
// if (!snapshot.hasData) {
// return Center(
// child: CircularProgressIndicator(
// valueColor: AlwaysStoppedAnimation<Color>(themeColor),
// ),
// );
// } else {
// return Container(
// width: 100,
// height: 200,
// child: ListView.builder(
// padding: EdgeInsets.all(10.0),
// itemBuilder: (context, index) =>
// buildItem(context, snapshot.data.documents),
// itemCount: snapshot.data.documents.length,
// ),
// );
// }
// },
// )
// : LoadingIndicator(
// loading: _isLoading,
// ),
// ),
child: Column(
children: <Widget>[
(_isLoading == false)
? (_areThereChats == true)
? Container(
child: AnimatedList(
key: _listKey,
shrinkWrap: true,
initialItemCount: arrayOfRequests.length,
itemBuilder:
(BuildContext context, index, animation) {
return _buildItem(context, arrayOfRequests[index],
animation, index);
},
),
)
: ChatAndRequestPlaceholder(
text: translate('when_someone_sends_you_a_request'))
: LoadingIndicator(
loading: _isLoading,
),
],
),
),
),
);
}
Widget buildItem(BuildContext context, DocumentSnapshot document) {
if (document['id'] == userId) {
return Container();
} else {
return Container(
child: FlatButton(
child: Row(
children: <Widget>[
Flexible(
child: Container(
child: Column(
children: <Widget>[
Container(
child: Text(
'Nickname: ${document['content']}',
style: TextStyle(color: primaryColor),
),
alignment: Alignment.centerLeft,
margin: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 5.0),
),
Container(
child: Text(
'About me: ${document['content'] ?? 'Not available'}',
style: TextStyle(color: primaryColor),
),
alignment: Alignment.centerLeft,
margin: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 0.0),
)
],
),
margin: EdgeInsets.only(left: 20.0),
),
),
],
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Chat(
peerId: document.documentID,
peerAvatar: document['photoUrl'],
),
),
);
},
color: greyColor2,
padding: EdgeInsets.fromLTRB(25.0, 10.0, 25.0, 10.0),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
),
margin: EdgeInsets.only(bottom: 10.0, left: 5.0, right: 5.0),
);
}
}
_buildItem(BuildContext context, ArrayOfRequestsModel arrayOfSentRequests,
Animation animation, int index) {
return GestureDetector(
onTap: () {
print(arrayOfSentRequests.profilePictureUrl);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Chat(
peerId: arrayOfSentRequests.userId,
peerAvatar: arrayOfSentRequests.profilePictureUrl,
),
),
);
},
child: Container(
height: 82,
child: ScaleTransition(
scale: animation,
child: Padding(
padding: EdgeInsets.fromLTRB(0, 10, 0, 0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.07),
offset: Offset(0, 5),
blurRadius: 11,
),
],
),
width: double.infinity,
height: 78,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Container(
height: double.infinity,
child: GestureDetector(
onTap: () {
Navigator.of(context, rootNavigator: true).push(
MaterialPageRoute(
builder: (context) => ProfileDetailsScreen(),
),
);
},
child: Row(
children: <Widget>[
SizedBox(width: 24),
ClipRRect(
borderRadius: BorderRadius.circular(25),
child: Image(
image: NetworkImage(
arrayOfSentRequests.profilePictureUrl,
),
width: 48,
),
),
SizedBox(width: 10),
Expanded(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
arrayOfSentRequests.nameAndSurname,
style: TextStyle(
color: Colors.black,
fontFamily: 'Avenir',
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
Text(
'11/11/2020', //arrayOfSentRequests.nameAndSurname
style: TextStyle(
color: Colors.grey,
fontFamily: 'Avenir',
fontWeight: FontWeight.w500,
fontSize: 15,
),
),
],
),
),
Container(
child: Text(
// (arrayOfSentRequests.lastMessage !=
// null)
// ? arrayOfSentRequests.lastMessage
// : 'Loading..',
(listOfLastMessages.isNotEmpty)
? listOfLastMessages[index]
.toString()
: 'Loading...',
maxLines: 2,
softWrap: true,
style: TextStyle(
color: Colors.black54,
fontFamily: 'Avenir',
fontSize: 15,
),
),
),
],
),
),
),
SizedBox(width: 10),
],
),
),
),
),
Container(
child: Center(
child: Icon(
Icons.keyboard_arrow_right,
size: 22,
color: Colors.black54,
),
),
),
SizedBox(width: 10),
],
),
),
),
),
),
);
}
}
Related
Here is my code;
class TaskList extends StatefulWidget {
#override
_TaskListState createState() => _TaskListState();
}
class _TaskListState extends State<TaskList> {
final _auth = FirebaseAuth.instance;
void getCurrentUser() async {
try {
final user = await _auth.currentUser;
if (user != null) {
loggedInUser = user;
}
} catch (e) {
print(e);
}
}
#override
void initState() {
// TODO: implement initState
super.initState();
getCurrentUser();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _firestore.collection('tasks').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Padding(
padding: EdgeInsets.only(top: 50, bottom: 50),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"There is nothing to show for now!",
style: GoogleFonts.poppins(fontSize: 20),
),
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddTask()),
);
},
child: Container(
height: 80,
width: 80,
decoration: BoxDecoration(
color: Colors.blueAccent,
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomRight,
colors: [Colors.blue, Colors.blueAccent],
),
shape: BoxShape.circle,
),
child: Center(
child: Icon(
Icons.add,
size: 35,
color: Colors.white,
),
),
),
),
],
),
);
} else {
return Padding(
padding:
EdgeInsets.only(top: 50, bottom: 50, left: 20, right: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: ListView(
children:
snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return Card(
child: Padding(
padding: EdgeInsets.only(
top: 10, bottom: 10, left: 10, right: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(data['title'],
style: kNormalTextStyle),
Text(
data['text'],
style: kNormalTextStyle,
),
Text(
data['date'],
style: kNormalTextStyle,
),
Text(
data['time'],
style: kNormalTextStyle,
),
],
),
),
IconButton(
icon: Icon(Icons.delete),
onPressed: () {},
)
],
),
),
);
}).toList(),
),
),
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddTask()),
);
},
child: Container(
height: 80,
width: 80,
decoration: BoxDecoration(
color: Colors.blueAccent,
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomRight,
colors: [Colors.blue, Colors.blueAccent],
),
shape: BoxShape.circle,
),
child: Center(
child: Icon(
Icons.add,
size: 35,
color: Colors.white,
),
),
),
),
],
),
);
}
});
}
}
What I'm tryin to do is if there is a task with email as same as loggedInUser, I want to show it AllTasks page.
return StreamBuilder(
stream: _firestore.collection('tasks').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData)
In this code part, I can check if there is any data in snapshot however, I want to check if there is a task with mail as same as loggedInUser.email. Is there a way to check if there is a task with email as same as loggedInUser?
loggedInUser implementation:
User? loggedInUser;
Also how I add data to firestore database:
ElevatedButton(
child: Text("Add Task"),
onPressed: () {
String formatedDate =
DateFormat("MM-dd-yyyy").format(_selectedDate!);
String formattedTime =
DateFormat("HH:mm").format(_dateTime!);
_firestore.collection("tasks").add({
'title': _titleController.text,
'text': _textController.text,
'date': formatedDate,
'time': formattedTime,
'email': loggedInUser!.email,
});
Navigator.pop(context, _firestore);
},
),
If you only want to show the tasks for the logged in user, it is best to use a query to only read those tasks from the database.
stream: _firestore.collection('tasks').where('email', isEqualTo: loggedInUser!.email).snapshots(),
Also see the FlutterFire documentation on filtering data through queries.
I have an issue with flutter, and the isComplet bool are not working well. When I press it twice in the app, it make the task as may times as you tap on there. How can I solve it? This is the entire code. The todos list is a list of tasks to do in a task app, but it works fine:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:mrplan/loading.dart';
import 'package:mrplan/services/database_services.dart';
import 'package:mrplan/src/models/todo.dart';
import 'package:mrplan/src/pages/graficas_circulares_page.dart';
import 'package:mrplan/src/widgets/appbar.dart';
bool isComplet = false;
TextEditingController todoTitleController = TextEditingController();
class Todolist extends StatefulWidget {
#override
_TodolistState createState() => _TodolistState();
}
class _TodolistState extends State<Todolist> {
TextEditingController todoTitleController = TextEditingController();
#override
Widget build(BuildContext context) {
setState(() {
});
return Scaffold(
body: SafeArea(
child: StreamBuilder<List<Todo>>(
stream: DatabaseService().listTodos(),
builder: (context, snapshot) {
if(!snapshot.hasData){
return Loading();
}
List<Todo> todos = snapshot.data;
if(todos.length == 0){
return Center(child: Text('No hay tareas agregadas!'));
}
return Padding(
padding:EdgeInsets.all(0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 20),
Expanded(
child: ListView.separated(
separatorBuilder: (context, index)=> Divider(color: Colors.white),
shrinkWrap: true,
itemCount: todos.length,
itemBuilder: (context, index){
return Dismissible(
key: UniqueKey(),
background: Container(padding: EdgeInsets.only(left: 20),
alignment: Alignment.centerLeft,
child: Icon(Icons.delete),
color: Colors.red),
onDismissed: (direction) async {
await DatabaseService().removeTodo(todos[index].uid);
quitar();
},
child: StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance.collection('Todos').doc().snapshots(),
builder: (context, snapshot) {
var todoss = snapshot.data;
return ListTile(
onTap: (){
isComplet = !isComplet;
if (Todo().isComplet = true)
{DatabaseService().completTask(todos[index].uid);}
if (Todo().isComplet = false)
{DatabaseService().uncompletTask(todos[index].uid);}},
leading: Container(
padding: EdgeInsets.all(2),
height: 30,
width: 30,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
shape: BoxShape.circle
),
child: todos[index].isComplet
? Icon(
Icons.check,
color: Colors.white,)
: Container(),
),
title: Text(
todos[index].title,
style: todos[index].isComplet
? TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
decoration: TextDecoration.lineThrough,
decorationColor: Colors.red,
)
: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
decoration: null
)
),
);
}
),
);
}
),
)
],
),
);
}
)
),
);
}
}
class Appbar extends StatefulWidget {
#override
_AppbarState createState() => _AppbarState();
}
class _AppbarState extends State<Appbar> {
#override
Widget build(BuildContext context) {
bool isbuttondisabled = false;
setState(() {
});
return StreamBuilder<Object>(
stream: null,
builder: (context, snapshot) {
return Container(
alignment: Alignment.bottomCenter,
child: BottomAppBar(
elevation: 0,
child: Container(
height: MediaQuery.of(context).size.height * 0.05,
width: MediaQuery.of(context).size.width,
child: Row(
children: [
Container(
decoration: BoxDecoration(
color:Colors.grey[100],
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(5.0),
)
),
height: MediaQuery.of(context).size.height,
padding: EdgeInsets.symmetric(horizontal: 10.0),
width: MediaQuery.of(context).size.width * 0.835,
child: Center(
child: TextFormField(
controller: todoTitleController,
autofocus: true,
decoration: InputDecoration(
hintStyle: TextStyle(color: Colors.blue[800]),
hintText: "Inserte una tarea...",
border: InputBorder.none
),
),
),
),
TextButton(
child: Container(
height: MediaQuery.of(context).size.height,
child: Center(child: Icon(Icons.add))),
style: TextButton.styleFrom(
primary: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: const Radius.circular(10.0),
)
),
backgroundColor: Theme.of(context).primaryColor
),
onPressed: isbuttondisabled ? null : ()async {
if(todoTitleController.text.isNotEmpty){
await DatabaseService().createNewTodo(todoTitleController.text.trim());
FocusScope.of(context).unfocus();
anadir();
todoTitleController.clear();
isComplet = !isComplet;
}
}
)
],
),
)
)
);
}
);
}
}
The relevant code where I am stucked is this:
onTap: (){
isComplet = !isComplet;
if (Todo().isComplet = true)
{DatabaseService().completTask(todos[index].uid);}
if (Todo().isComplet = false)
{DatabaseService().uncompletTask(todos[index].uid);}},
Thanks a lot!
It would be better if you could refactor the code a little bit. But heres the problem
remove this code inside the build method
bool isbuttondisabled = false;
setState(() {
});
add bool isbuttondisabled = false;
outside the build method.
where ever your button is,
in the onPressed parameter
setState(() {
isbuttondisabled=true;
//Todo add your code
});```
I have a problem with StreamBuilder. I using FirebaseFirestore and I want to make a quiz app but I don't change my element's color. I used the setState method but it didn't work. How to change element color on Stream Builder?
My codes:
https://paste.tc/dtckRClOTL
import 'package:audioplayers/audio_cache.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dots_indicator/dots_indicator.dart';
import 'package:exampills/screens/tests/resultScreen.dart';
import 'package:exampills/providers/testProvider.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:random_color/random_color.dart';
class TestScreen extends StatefulWidget {
final String categoryId;
final String subcategoryId;
final String title;
final String testId;
final int questPiece;
const TestScreen(
{Key key,
this.categoryId,
this.subcategoryId,
this.testId,
this.title,
this.questPiece})
: super(key: key);
#override
_TestScreenState createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
int position;
AudioCache audioCache = AudioCache();
AudioPlayer audioPlayer = AudioPlayer();
RandomColor randomColor = RandomColor();
var controller = Get.put(TestProvider());
PageController pageController = PageController();
int testQuestPiece = 0;
Future nextPage() {
pageController.animateToPage(pageController.page.toInt() + 1,
duration: Duration(milliseconds: 400), curve: Curves.easeIn);
}
void previousPage() {
pageController.animateToPage(pageController.page.toInt() - 1,
duration: Duration(milliseconds: 400), curve: Curves.easeIn);
}
getDocs() async {
await FirebaseFirestore.instance
.collection(
"categories/${widget.categoryId}/subcategories/${widget.subcategoryId}/tests/${widget.testId}/questions")
.get()
.then((value) {
setState(() {
testQuestPiece = value.docs.length;
});
});
}
#override
void initState() {
print(controller.position);
controller.updatePosition(0);
getDocs();
super.initState();
}
#override
Widget build(BuildContext context) {
Color color = randomColor.randomColor();
int selectedItem;
bool selectedBool = false;
return Scaffold(
backgroundColor: color,
bottomNavigationBar: Container(
child: DotsIndicator(
dotsCount: testQuestPiece,
position: controller.position,
axis: Axis.horizontal,
reversed: false,
decorator: DotsDecorator(
color: Colors.white,
activeColor: randomColor.randomColor(),
size: const Size.square(9.0),
activeShape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0)),
activeSize: const Size(18.0, 18.0),
),
mainAxisAlignment: MainAxisAlignment.center,
)),
appBar: AppBar(
centerTitle: true,
title: Text(
widget.title,
style: TextStyle(fontWeight: FontWeight.bold),
),
leading: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(Icons.arrow_back_ios),
color: Colors.white,
),
backgroundColor: Colors.transparent,
actions: [
IconButton(
icon: Icon(Icons.timer),
onPressed: () {},
)
]),
body: Stack(
fit: StackFit.expand,
children: [
StreamBuilder(
stream: FirebaseFirestore.instance
.collection(
"categories/${widget.categoryId}/subcategories/${widget.subcategoryId}/tests/${widget.testId}/questions")
.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return PageView(
physics: NeverScrollableScrollPhysics(),
allowImplicitScrolling: false,
scrollDirection: Axis.horizontal,
controller: pageController,
onPageChanged: controller.updatePosition,
children: snapshot.data.docs.map<Widget>((data) {
var datas = data.data();
return Stack(
fit: StackFit.expand,
children: [
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 350,
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30)),
height: 200,
child: Text(datas['questionTitle'],
style: TextStyle(
color: Colors.black, fontSize: 20))),
SizedBox(
height: 15,
),
Expanded(
child: ListView.builder(
itemCount: datas['answers'].length,
itemBuilder: (context, index) {
return Column(
children: [
InkWell(
onTap: () async {
if (datas['trueAnswer'] == index) {
audioCache
.play("sounds/correct.wav");
} else {
audioCache.play("sounds/wrong.wav");
setState(() {
selectedItem =
datas['trueAnswer'];
selectedBool = true;
});
}
await Future.delayed(
Duration(seconds: 2))
.then((value) {
print(pageController.page.toInt());
print(testQuestPiece);
if ((pageController.page.toInt() +
1) ==
testQuestPiece) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) =>
ResultScreen()));
} else {
nextPage();
}
});
},
child: Container(
width: 350,
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: selectedBool == true
? (selectedItem == index
? Colors.green
: Colors.red)
: Colors.white,
borderRadius:
BorderRadius.circular(30)),
height: 80,
child: Text(
"${index + 1}. " +
datas['answers'][index],
style: TextStyle(
color: Colors.black,
fontSize: 20)),
),
),
SizedBox(
height: 15,
),
],
);
},
),
),
],
),
),
],
);
}).toList(),
);
},
),
],
),
);
}
}
I was learning on flutter how to perform CRUD operations using BLoC pattern, I saw a tutorial online and try to apply it. C,R,U are OK, but deletion is not working. this is the source code.
Please Help !
file todo_bloc.dart
import 'package:zencartos/models/todo.dart';
import 'package:zencartos/repository/todo_repository.dart';
class TodoBloc {
//Get instance of the Repository
final _todoRepository = TodoRepository();
final _todoController = StreamController<List<Todo>>.broadcast();
get todos => _todoController.stream;
TodoBloc() {
getTodos();
}
getTodos({String query}) async {
_todoController.sink.add(await _todoRepository.getAllTodos(query: query));
}
addTodo(Todo todo) async {
await _todoRepository.insertTodo(todo);
getTodos();
}
updateTodo(Todo todo) async {
await _todoRepository.updateTodo(todo);
getTodos();
}
deleteTodoById(int id) async {
_todoRepository.deleteTodoById(id);
getTodos();
}
dispose() {
_todoController.close();
}
}
DAO Page todo_dao.dart
import 'package:zencartos/database/database.dart';
import 'package:zencartos/models/todo.dart';
class TodoDao {
final dbProvider = DatabaseProvider.dbProvider;
Future<int> createTodo(Todo todo) async{
final db = await dbProvider.database;
var result = db.insert(todoTABLE, todo.toDatabaseJson());
return result;
}
Future<List<Todo>> getTodos({List<String> columns, String query})
async{
final db = await dbProvider.database;
List<Map<String, dynamic>> result;
if (query != null){
if (query.isNotEmpty)
result = await db.query(todoTABLE, columns: columns, where: 'description LIKE ?', whereArgs: ["%$query%"]);
} else {
result = await db.query(todoTABLE, columns: columns);
}
List<Todo> todos = result.isNotEmpty
? result.map((item)=> Todo.fromDatabaseJson(item)).toList()
: [];
return todos;
}
//Update Todo record
Future<int> updateTodo(Todo todo) async{
final db = await dbProvider.database;
var result = await db.update(todoTABLE, todo.toDatabaseJson(),
where: "id = ?", whereArgs: [todo.id]);
return result;
}
//Delete Todo records
Future<int> deleteTodo(int id) async{
final db = await dbProvider.database;
var result = await db.delete(todoTABLE, where: 'id = ?', whereArgs: [id]);
return result;
}
}
Database Page database.dart
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
final todoTABLE = 'Todo';
class DatabaseProvider {
static final DatabaseProvider dbProvider = DatabaseProvider();
Database _database;
Future<Database> get database async {
if (_database != null) return _database;
_database = await createDatabase();
return _database;
}
createDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "ReactiveTodo");
var database = await openDatabase(path, version: 1, onCreate: initDB, onUpgrade: onUpgrade);
return database;
}
void onUpgrade(Database database, int oldVersion, int newVersion){
if (newVersion > oldVersion){}
}
void initDB(Database database, int version) async{
await database.execute("CREATE TABLE $todoTABLE ("
"id INTEGER PRIMARY KEY, "
"description TEXT, "
"is_done INTEGER "
")");
}
}
Model page todo.dart
class Todo{
int id;
String description;
bool isDone = false;
Todo({this.id, this.description, this.isDone = false});
factory Todo.fromDatabaseJson(Map<String, dynamic> data) => Todo(
id: data['data'],
description: data['description'],
isDone: data['is_done'] == 0 ? false : true,
);
Map<String, dynamic> toDatabaseJson() => {
"id": this.id,
"description": this.description,
"is_done": this.isDone == false ? 0 : 1,
};
}
View page home_Page2.dart
import 'package:flutter/services.dart';
import 'package:zencartos/bloc/todo_bloc.dart';
import 'package:zencartos/models/todo.dart';
class HomePage2 extends StatelessWidget {
HomePage2({Key key, this.title}) : super(key: key);
final TodoBloc todoBloc = TodoBloc();
final String title;
//Allows Todo card to be dismissable horizontally
final DismissDirection _dismissDirection = DismissDirection.horizontal;
#override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark.copyWith(
statusBarColor: Colors.white,
systemNavigationBarColor: Colors.white,
systemNavigationBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.dark));
return Scaffold(
resizeToAvoidBottomPadding: false,
body: SafeArea(
child: Container(
color: Colors.white,
padding:
const EdgeInsets.only(left: 2.0, right: 2.0, bottom: 2.0),
child: Container(
//This is where the magic starts
child: getTodosWidget()))),
bottomNavigationBar: BottomAppBar(
color: Colors.white,
child: Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: Colors.grey, width: 0.3),
)),
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
IconButton(
icon: Icon(
Icons.menu,
color: Colors.indigoAccent,
size: 28,
),
onPressed: () {
//just re-pull UI for testing purposes
todoBloc.getTodos();
}),
Expanded(
child: Text(
"Todo",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontFamily: 'RobotoMono',
fontStyle: FontStyle.normal,
fontSize: 19),
),
),
Wrap(children: <Widget>[
IconButton(
icon: Icon(
Icons.search,
size: 28,
color: Colors.indigoAccent,
),
onPressed: () {
_showTodoSearchSheet(context);
},
),
Padding(
padding: EdgeInsets.only(right: 5),
)
])
],
),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: Padding(
padding: EdgeInsets.only(bottom: 25),
child: FloatingActionButton(
elevation: 5.0,
onPressed: () {
_showAddTodoSheet(context);
},
backgroundColor: Colors.white,
child: Icon(
Icons.add,
size: 32,
color: Colors.indigoAccent,
),
),
));
}
void _showAddTodoSheet(BuildContext context) {
final _todoDescriptionFormController = TextEditingController();
showModalBottomSheet(
context: context,
builder: (builder) {
return new Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: new Container(
color: Colors.transparent,
child: new Container(
height: 230,
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(10.0),
topRight: const Radius.circular(10.0))),
child: Padding(
padding: EdgeInsets.only(
left: 15, top: 25.0, right: 15, bottom: 30),
child: ListView(
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextFormField(
controller: _todoDescriptionFormController,
textInputAction: TextInputAction.newline,
maxLines: 4,
style: TextStyle(
fontSize: 21, fontWeight: FontWeight.w400),
autofocus: true,
decoration: const InputDecoration(
hintText: 'I have to...',
labelText: 'New Todo',
labelStyle: TextStyle(
color: Colors.indigoAccent,
fontWeight: FontWeight.w500)),
validator: (String value) {
if (value.isEmpty) {
return 'Empty description!';
}
return value.contains('')
? 'Do not use the # char.'
: null;
},
),
),
Padding(
padding: EdgeInsets.only(left: 5, top: 15),
child: CircleAvatar(
backgroundColor: Colors.indigoAccent,
radius: 18,
child: IconButton(
icon: Icon(
Icons.save,
size: 22,
color: Colors.white,
),
onPressed: () {
final newTodo = Todo(
description:
_todoDescriptionFormController
.value.text);
if (newTodo.description.isNotEmpty) {
/*Create new Todo object and make sure
the Todo description is not empty,
because what's the point of saving empty
Todo
*/
todoBloc.addTodo(newTodo);
//dismisses the bottomsheet
Navigator.pop(context);
}
},
),
),
)
],
),
],
),
),
),
),
);
});
}
void _showTodoSearchSheet(BuildContext context) {
final _todoSearchDescriptionFormController = TextEditingController();
showModalBottomSheet(
context: context,
builder: (builder) {
return new Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: new Container(
color: Colors.transparent,
child: new Container(
height: 230,
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(10.0),
topRight: const Radius.circular(10.0))),
child: Padding(
padding: EdgeInsets.only(
left: 15, top: 25.0, right: 15, bottom: 30),
child: ListView(
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextFormField(
controller: _todoSearchDescriptionFormController,
textInputAction: TextInputAction.newline,
maxLines: 4,
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w400),
autofocus: true,
decoration: const InputDecoration(
hintText: 'Search for todo...',
labelText: 'Search *',
labelStyle: TextStyle(
color: Colors.indigoAccent,
fontWeight: FontWeight.w500),
),
validator: (String value) {
return value.contains('#')
? 'Do not use the # char.'
: null;
},
),
),
Padding(
padding: EdgeInsets.only(left: 5, top: 15),
child: CircleAvatar(
backgroundColor: Colors.indigoAccent,
radius: 18,
child: IconButton(
icon: Icon(
Icons.search,
size: 22,
color: Colors.white,
),
onPressed: () {
/*This will get all todos
that contains similar string
in the textform
*/
todoBloc.getTodos(
query:
_todoSearchDescriptionFormController
.value.text);
//dismisses the bottomsheet
Navigator.pop(context);
},
),
),
)
],
),
],
),
),
),
),
);
});
}
Widget getTodosWidget() {
return StreamBuilder(
stream: todoBloc.todos,
builder: (BuildContext context, AsyncSnapshot<List<Todo>> snapshot) {
return getTodoCardWidget(snapshot);
},
);
}
Widget getTodoCardWidget(AsyncSnapshot<List<Todo>> snapshot) {
if (snapshot.hasData) {
return snapshot.data.length != 0
? ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, itemPosition) {
Todo todo = snapshot.data[itemPosition];
final Widget dismissibleCard = new Dismissible(
background: Container(
child: Padding(
padding: EdgeInsets.only(left: 10),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
"Deleting",
style: TextStyle(color: Colors.white),
),
),
),
color: Colors.redAccent,
),
onDismissed: (direction) {
todoBloc.deleteTodoById(todo.id);
},
direction: _dismissDirection,
key: new ObjectKey(todo),
child: Card(
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.grey[200], width: 0.5),
borderRadius: BorderRadius.circular(5),
),
color: Colors.white,
child: ListTile(
leading: InkWell(
onTap: () {
//Reverse the value
todo.isDone = !todo.isDone;
todoBloc.updateTodo(todo);
},
child: Container(
//decoration: BoxDecoration(),
child: Padding(
padding: const EdgeInsets.all(15.0),
child: todo.isDone
? Icon(
Icons.done,
size: 26.0,
color: Colors.indigoAccent,
)
: Icon(
Icons.check_box_outline_blank,
size: 26.0,
color: Colors.tealAccent,
),
),
),
),
title: Text(
todo.description,
style: TextStyle(
fontSize: 16.5,
fontFamily: 'RobotoMono',
fontWeight: FontWeight.w500,
decoration: todo.isDone
? TextDecoration.lineThrough
: TextDecoration.none),
),
)),
);
return dismissibleCard;
},
)
: Container(
child: Center(
//this is used whenever there 0 Todo
//in the data base
child: noTodoMessageWidget(),
));
} else {
return Center(
child: loadingData(),
);
}
}
Widget loadingData() {
todoBloc.getTodos();
return Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(),
Text("Loading...",
style: TextStyle(fontSize: 19, fontWeight: FontWeight.w500))
],
),
),
);
}
Widget noTodoMessageWidget() {
return Container(
child: Text(
"Start adding Todo...",
style: TextStyle(fontSize: 19, fontWeight: FontWeight.w500),
),
);
}
dispose() {
todoBloc.dispose();
}
}
repository page todo_repository.dart
import 'package:zencartos/models/todo.dart';
class TodoRepository {
final todoDao = TodoDao();
Future getAllTodos({String query}) => todoDao.getTodos(query: query);
Future insertTodo(Todo todo) => todoDao.createTodo(todo);
Future updateTodo(Todo todo) => todoDao.updateTodo(todo);
Future deleteTodoById(int id) => todoDao.deleteTodo(id);
//We are not going to use this in the demo
//Future deleteAllTodos() => todoDao.deleteAllTodos();
}
In your todo_bloc.dart, your delete method should be:
deleteTodoById(int id) async {
await _todoRepository.deleteTodoById(id);
getTodos();
}
I have a problem querying my geolocation data from my database in Cloud Firestore. I went through the documentation on Youtube and came to the conclusion that it will work best for me when i save the geolocation data in subcollections.
Here is my databse structure:
And if you go into one of the documents in the subcollection:
The database itself has a collection called "tourguides", with each document containing basic info like the name of the tour and the region where the tour is (both are Strings). Each of the documents then have a subcollection called "locations", where each document has the Strings "Name" and "ID" and also a geopoint with latitude and longitude data.
The documents from the "Tourguides" collection are shown in a ListView. Whenever i tap on one of the entries, a map shall open where all the markers from the respective subcollection are shown.
Here is my ListView Builder:
#override
void initState() {
super.initState();
_pointsofinterest = Firestore.instance.collection('tourguides').document('sydney_downtown_guide').col lection('locations').orderBy('name').snapshots();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<QuerySnapshot>(
stream: _pointsofinterest,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
return new ListView(
children:
snapshot.data.documents.map((DocumentSnapshot document) {
return InkWell(
child: new ListTile(
title: new Text(document['name']),
subtitle: new Text(document['region']),
onTap: () {
return TourMap(
documents: snapshot.data.documents,
initialPosition: const LatLng(-33.868210, 151.208391),
mapController: _mapController,
);
},
),
);
}).toList(),
);
}
},
),
);
}
I put my map into a StatlessWidget (I am not sure. Maybe this has to be a StatefulWidget?):
class TourMap extends StatelessWidget {
const TourMap({
Key key,
#required this.documents,
#required this.initialPosition,
#required this.mapController,
}) : super(key: key);
final List<DocumentSnapshot> documents;
final LatLng initialPosition;
final Completer<GoogleMapController> mapController;
#override
Widget build(BuildContext context) {
return GoogleMap(
initialCameraPosition: CameraPosition(
target: initialPosition,
zoom: 12,
),
markers: documents
.map((document) => Marker(
markerId: MarkerId(document['placeId']),
icon: BitmapDescriptor.defaultMarker,
position: LatLng(
document['geolocation'].latitude,
document['geolocation'].longitude,
),
infoWindow: InfoWindow(
title: document['location_name'],
),
))
.toSet(),
onMapCreated: (mapController) {
this.mapController.complete(mapController);
},
);
}}
Now i dont exactly know how to set up the query in my OnTap function. The Firestore documentation showed that i always have to refer to specfic documents if i go down the collections from my database.
For example (collection/document/collecion). But in my query, the "document" in the middle of the path is always different, depending on which tourguide the user clicks.
Any ideas on that? Looking Forward to your replies!
UPDATE: I slightly configured my database structure! I now use two seperate databases. One database holds information about the available tourguides (just two Strings currently: Name and Region) and the other stores the actual individual Locations.
I now use where-queries to get the correct locations based on the tourguide´s Name they´re belonging to.
The query itself works on the OnTap function now:
return new ListView(
children:
snapshot.data.documents.map((DocumentSnapshot document) {
return InkWell(
child: new ListTile(
title: new Text(document['name']),
subtitle: new Text(document['region']),
onTap: () {
Firestore.instance.collection('locations').where(
"toActivity",
isEqualTo: document['name'],
)
.snapshots()
.listen((data) =>
data.documents.forEach((doc) => print(doc["location_name"])));
},
),
);
}).toList(),
);
The database structure:
The correct entries are printed into the console if i tap on one of the entries in the ListView. But i will need a Google Map to pop up that shows the appropriate markers based on the "geolocation" values from the database.
Bro, I did. I can retrieve it by two ways.
1. manually by using simple 'initState'.
2. second by using provider (but by using the second method, i have not succeed to show the marker, yet). Hope can help you, although it was long time ago. Here is mine by using 'initState':
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:provider/provider.dart';
import 'package:visitmalang/provider/aktivitas_provider.dart';
import 'package:visitmalang/ui/home/beranda/aktivitas/profil_agen_wisata.dart';
import 'package:visitmalang/ui/widget/textcustom.dart';
class MapAktivitas extends StatefulWidget {
#override
_MapAktivitasState createState() => _MapAktivitasState();
}
class _MapAktivitasState extends State<MapAktivitas> {
Map<MarkerId, Marker> markers = <MarkerId, Marker>{};
GoogleMapController mapController;
bool mapToggle = false;
bool geraiToggle = false;
var currentLocation;
var clients = [];
#override
void initState() {
// TODO: implement initState
super.initState();
Geolocator().getCurrentPosition().then((lokasiSekarang) {
setState(() {
currentLocation = lokasiSekarang;
mapToggle = true;
populateClients();
});
});
}
populateClients() {
clients = [];
Firestore.instance.collection('trail').getDocuments().then((docs) {
if (docs.documents.isNotEmpty) {
setState(() {
geraiToggle = true;
});
for (int i = 0; i < docs.documents.length; i++) {
clients.add(docs.documents[i].data);
initMarker(docs.documents[i].data, docs.documents[i].documentID);
}
}
});
}
void initMarker(request, requestId) {
var markerIdVal = requestId;
final MarkerId markerId = MarkerId(markerIdVal);
final Marker marker = Marker(
markerId: markerId,
position: LatLng(
request['koordinat'].latitude, request['koordinat'].longitude),
infoWindow: InfoWindow(title: request['nama']));
setState(() {
markers[markerId] = marker;
});
}
Widget clientCard(client) {
return Padding(
padding: const EdgeInsets.only(left: 8.0, top: 8.0),
child: InkWell(
onTap: () {
zoomInMarker(client);
},
child: Stack(
alignment: FractionalOffset(0.5, 0.94),
children: <Widget>[
Material(
elevation: 4.0,
borderRadius: BorderRadius.circular(10.0),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: Colors.white,
),
height: 108,
width: 200,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
//
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
bottomLeft: Radius.circular(10.0)),
child: Container(
width: 96,
height: 108,
child: Image.asset(
'assets/trail.png',
fit: BoxFit.cover,
),
),
),
SizedBox(
width: 4,
),
Stack(
alignment: FractionalOffset(0.5, 0.9),
children: <Widget>[
Container(
width: 100,
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0),
child: Container(
alignment: Alignment.center,
child: textCustom(client['nama'],
Colors.black87, 14, 'Montserrat'),
),
),
],
),
),
),
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: InkWell(
onTap: () => Navigator.of(context).push(
CupertinoPageRoute(
builder: (BuildContext context) =>
ProfilAgenWisata(
nama: client['nama'],
deskripsi: client['deskripsi'],
website: client['website'],
email: client['email'],
noTelepon: client['noTelepon'],
whatsApp: client['whatsApp'],
alamat: client['alamat'],
fasilitas: client['fasilitas'],
))),
child: Container(
alignment: Alignment.center,
color: Color(0xFFDB5C48),
height: 40,
width: 88,
child: textCustom(
'Detail', Colors.white, 14, 'Montserrat'),
),
),
)
],
),
],
),
),
),
],
)),
);
}
zoomInMarker(client) {
mapController.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(
client['koordinat'].latitude, client['koordinat'].longitude),
zoom: 16.0,
bearing: 19.0,
tilt: 15.0),
),
);
}
#override
Widget build(BuildContext context) {
// AktivitasNotifier aktivitasNotifier = Provider.of<AktivitasNotifier>(context);
return Scaffold(
appBar: AppBar(
title: textCustom('Ngetrail', Colors.black87, 18, 'Montserrat'),
centerTitle: true,
elevation: 0.0,
backgroundColor: Colors.transparent.withOpacity(0.0),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios, color: Colors.black87),
onPressed: (){Navigator.pop(context);},),
),
body: Stack(
children: <Widget>[
Column(
children: <Widget>[
Container(
width: double.infinity,
height: MediaQuery.of(context).size.height - 80,
child: mapToggle
? GoogleMap(
myLocationEnabled: true,
myLocationButtonEnabled: true,
markers: Set<Marker>.of(markers.values),
compassEnabled: false,
zoomControlsEnabled: false,
mapType: MapType.normal,
initialCameraPosition: CameraPosition(
target: LatLng(currentLocation.latitude,
currentLocation.longitude),
zoom: 15),
onMapCreated: (controller) {
setState(() {
mapController = controller;
});
},
)
: Center(
child: textCustom(
'Loading...', Colors.black87, 20, 'Hind')),
),
],
),
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Container(
height: 140.0,
width: MediaQuery.of(context).size.width,
child: geraiToggle
? ListView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.all(8.0),
children: clients.map((element) {
return clientCard(element);
}).toList(),
)
: Container(),
),
SizedBox(
height: 56.0,
)
],
)
],
));
}
}
Here is the second method, by using provider.
First, you have to decide the model (just focus on 'koordinat'):
class ModelAktivitas {
String idAktivitas;
var koordinat;
String nama;
String deskripsi;
String alamat;
String email;
String noTelepon;
String website;
String gambarUtama;
List galeri;
List fasilitas;
ModelAktivitas.fromMap(Map<String, dynamic> data) {
idAktivitas = data['idAktivitas'];
koordinat = data['koordinat'];
nama = data['nama'];
deskripsi = data['deskripsi'];
noTelepon = data['nomorTelepon'];
galeri = data['galeri'];
fasilitas = data['fasilitas'];
alamat = data['alamat'];
email = data['alamat'];
gambarUtama = data['gambarUtama'];
website = data['website'];
}
}
second, make provider for it:
import 'dart:collection';
import 'package:flutter/cupertino.dart';
import 'package:visitmalang/models/aktivitas_model.dart';
class AktivitasNotifier with ChangeNotifier {
List<ModelAktivitas> _listAktivitas = [];
ModelAktivitas _detailAktivitas;
UnmodifiableListView<ModelAktivitas> get listAktivitas =>
UnmodifiableListView(_listAktivitas);
ModelAktivitas get detailAktivitas => _detailAktivitas;
set listAktivitas(List<ModelAktivitas> listAktivitas) {
_listAktivitas = listAktivitas;
notifyListeners();
}
set detailAktivitas(ModelAktivitas aktivitas) {
_detailAktivitas = aktivitas;
notifyListeners();
}
}
After that, add the "get" from your firestore services:
getListAktivitas(AktivitasNotifier aktivitasNotifier) async {
QuerySnapshot snapshot =
await Firestore.instance.collection('trail').getDocuments();
List<ModelAktivitas> _listAktivitas = [];
snapshot.documents.forEach((doc) {
ModelAktivitas modelAktivitas = ModelAktivitas.fromMap(doc.data);
_listAktivitas.add(modelAktivitas);
});
aktivitasNotifier.listAktivitas = _listAktivitas;
}
then, the last step stream it to your UI code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:provider/provider.dart';
import 'package:visitmalang/provider/aktivitas_provider.dart';
import 'package:visitmalang/ui/home/beranda/aktivitas/profil_agen_wisata.dart';
import 'package:visitmalang/ui/widget/textcustom.dart';
import 'package:visitmalang/service/user_service.dart';
class MapAktivitasTandingan extends StatefulWidget {
#override
_MapAktivitasTandinganState createState() => _MapAktivitasTandinganState();
}
class _MapAktivitasTandinganState extends State<MapAktivitasTandingan> {
Map<MarkerId, Marker> markers = <MarkerId, Marker>{};
GoogleMapController mapController;
bool mapToggle = false;
bool geraiToggle = false;
var currentLocation;
#override
void initState() {
// TODO: implement initState
super.initState();
Geolocator().getCurrentPosition().then((lokasiSekarang) {
setState(() {
currentLocation = lokasiSekarang;
mapToggle = true;
});
});
AktivitasNotifier aktivitasNotifier =
Provider.of<AktivitasNotifier>(context, listen: false);
getListAktivitas(aktivitasNotifier);
}
#override
Widget build(BuildContext context) {
AktivitasNotifier aktivitasNotifier =
Provider.of<AktivitasNotifier>(context);
return Scaffold(
appBar: AppBar(
title: textCustom('Ngetrail', Colors.black87, 18, 'Montserrat'),
centerTitle: true,
elevation: 0.0,
backgroundColor: Colors.transparent.withOpacity(0.0),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios, color: Colors.black87),
onPressed: () {
Navigator.pop(context);
},
),
),
body: Stack(
children: <Widget>[
Column(
children: <Widget>[
Container(
width: double.infinity,
height: MediaQuery.of(context).size.height - 80,
child: mapToggle
? GoogleMap(
myLocationEnabled: true,
myLocationButtonEnabled: true,
markers: {
},
compassEnabled: false,
zoomControlsEnabled: false,
mapType: MapType.normal,
initialCameraPosition: CameraPosition(
target: LatLng(currentLocation.latitude,
currentLocation.longitude),
zoom: 15),
onMapCreated: (controller) {
setState(() {
mapController = controller;
});
},
)
: Center(
child: textCustom(
'Loading...', Colors.black87, 20, 'Hind')),
),
],
),
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Container(
height: 140.0,
width: MediaQuery.of(context).size.width,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: aktivitasNotifier.listAktivitas.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.only(left: 8.0, top: 8.0),
child: InkWell(
onTap: () {
mapController.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(
aktivitasNotifier
.listAktivitas[index]
.koordinat
.latitude,
aktivitasNotifier
.listAktivitas[index]
.koordinat
.longitude),
zoom: 16.0,
bearing: 19.0,
tilt: 15.0),
),
);
},
child: Stack(
alignment: FractionalOffset(0.5, 0.94),
children: <Widget>[
Material(
elevation: 4.0,
borderRadius: BorderRadius.circular(10.0),
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(10.0),
color: Colors.white,
),
height: 108,
width: 200,
child: Row(
crossAxisAlignment:
CrossAxisAlignment.center,
children: <Widget>[
//
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
bottomLeft:
Radius.circular(10.0)),
child: Container(
width: 96,
height: 108,
child: Image.asset(
'assets/trail.png',
fit: BoxFit.cover,
),
),
),
SizedBox(
width: 4,
),
Stack(
alignment:
FractionalOffset(0.5, 0.9),
children: <Widget>[
Container(
width: 100,
child: Padding(
padding:
const EdgeInsets.only(
top: 8.0),
child: Column(
children: <Widget>[
Padding(
padding:
const EdgeInsets
.symmetric(
horizontal:
8.0),
child: Container(
alignment:
Alignment.center,
child: textCustom(
aktivitasNotifier
.listAktivitas[
index]
.nama,
Colors.black87,
14,
'Montserrat'),
),
),
],
),
),
),
ClipRRect(
borderRadius:
BorderRadius.circular(10),
child: InkWell(
onTap: () => Navigator.of(
context)
.push(CupertinoPageRoute(
builder: (BuildContext
context) =>
ProfilAgenWisata(
nama: aktivitasNotifier
.listAktivitas[
index]
.nama,
website: aktivitasNotifier
.listAktivitas[
index]
.website,
noTelepon: aktivitasNotifier
.listAktivitas[
index]
.noTelepon,
email: aktivitasNotifier
.listAktivitas[
index]
.email,
alamat: aktivitasNotifier
.listAktivitas[
index]
.alamat,
deskripsi: aktivitasNotifier
.listAktivitas[
index]
.deskripsi,
))),
child: Container(
alignment: Alignment.center,
color: Color(0xFFDB5C48),
height: 40,
width: 88,
child: textCustom(
'Detail',
Colors.white,
14,
'Montserrat'),
),
),
)
],
),
],
),
),
),
],
)),
);
},
)),
SizedBox(
height: 56.0,
)
],
)
],
));
}
}