I am working a habit tracker app using flutter with sqflite. if the task is checked then it shows the task completing bar. in the bar graph whenever i opened the same task by clicking on it another day the bar will automatically should display the bar for that particular task. i am trying to display the previous week bar, but i am not able to display the previous week performance bar. the previous day bar also not displaying. in some of the tasks when i click on same task next day it just only shows for current day progress bar. i want that whenever i swipe left the previous week days task performance should display in the bar graph as bar in blue color.
The main problem i am facing in the two methods which may be fetchProgressDataForPreviousWeek
and fetchProgressDataForWeek
.
-
task has been added from habit screen and display in a listview format in the habitscreen
-
after clicking on particular task in habit screen navigate to Taskscreen in which performance of the task is displayed with of bar in a bar graph
-
I used getx state management
TaskScreen.dart
//TaskScreen.dart
class TaskScreen extends StatelessWidget {
final String taskName;
final bool isTaskChecked;
TaskScreen({required this.taskName, required this.isTaskChecked});
@override
Widget build(BuildContext context) {
final TaskController taskController = Get.find();
return Scaffold(
appBar: AppBar(
title: Text("Task Details"),
backgroundColor: Colors.blue,
),
body: PageView(
children: [
// Current Week Graph
FutureBuilder<List<double>>(
future: taskController.fetchProgressDataForWeek(taskName),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No data available for this week'));
}
if (!isTaskChecked) {
return Center(child: Text('No data available for this task.'));
}
final dailyProgress = snapshot.data!;
final daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
final today = DateTime.now();
final currentDayIndex = (today.weekday - DateTime.monday + 7) % 7;
final progressData = List<double>.filled(7, 0.0);
for (int i = 0; i < dailyProgress.length; i++) {
progressData[i] = dailyProgress[i];
}
final barGroups = progressData.asMap().entries.map((entry) {
int index = entry.key;
double value = entry.value;
Color barColor = Colors.blue;
double barHeight = value;
if (index < currentDayIndex) {
if (value == 0.0) {
barColor = Colors.grey;
barHeight = 0.2;
} else {
barColor = Colors.blue;
barHeight = value;
}
} else if (index > currentDayIndex) {
// Future days should not be shown
barColor = Colors.transparent;
barHeight = 0.0;
}
return BarChartGroupData(
x: index,
barRods: [
BarChartRodData(
toY: barHeight,
color: barColor,
width: 20,
borderRadius: BorderRadius.circular(4),
),
],
);
}).toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Current Week',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 20),
Container(
height: 300,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: progressData.isNotEmpty ? progressData.reduce((a, b) => a > b ? a : b) + 1 : 1,
barGroups: barGroups,
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (value, meta) {
final dayLabel = daysOfWeek[value.toInt() % 7];
return SideTitleWidget(
axisSide: meta.axisSide,
child: Text(dayLabel, style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
);
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (value, meta) {
return SideTitleWidget(
axisSide: meta.axisSide,
child: Text('${value.toInt()}', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
);
},
),
),
),
borderData: FlBorderData(
show: true,
border: Border.all(color: const Color(0xff37434d), width: 1),
),
gridData: FlGridData(show: true),
),
),
),
],
);
},
),
// Previous Week Graph
FutureBuilder<List<double>>(
future: taskController.fetchProgressDataForPreviousWeek(taskName),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No data available for the previous week'));
}
if (!isTaskChecked) {
return Center(child: Text('No data available for this task.'));
}
final dailyProgress = snapshot.data!;
final daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
final progressData = List<double>.filled(7, 0.0);
for (int i = 0; i < dailyProgress.length; i++) {
progressData[i] = dailyProgress[i];
}
final barGroups = progressData.asMap().entries.map((entry) {
int index = entry.key;
double value = entry.value;
Color barColor = value == 1.0 ? Colors.blue : Colors.grey;
double barHeight = value == 1.0 ? 1.0 : 0.1;
return BarChartGroupData(
x: index,
barRods: [
BarChartRodData(
toY: barHeight,
color: barColor,
width: 20,
borderRadius: BorderRadius.circular(4),
),
],
);
}).toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Previous Week',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 20),
Container(
height: 300,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: progressData.isNotEmpty ? progressData.reduce((a, b) => a > b ? a : b) + 1 : 1,
barGroups: barGroups,
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (value, meta) {
final dayLabel = daysOfWeek[value.toInt() % 7];
return SideTitleWidget(
axisSide: meta.axisSide,
child: Text(dayLabel, style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
);
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (value, meta) {
return SideTitleWidget(
axisSide: meta.axisSide,
child: Text('${value.toInt()}', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
);
},
),
),
),
borderData: FlBorderData(
show: true,
border: Border.all(color: const Color(0xff37434d), width: 1),
),
gridData: FlGridData(show: true),
),
),
),
],
);
},
),
],
),
);
}
}
DatabaseHelper.dart
import 'package:intl/intl.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
factory DatabaseHelper() => _instance;
DatabaseHelper._internal();
static Database? _database;
// Static constants for table and column names
static const String _tableTasks = 'tasks';
static const String _columnId = 'id';
static const String _columnTask = 'task';
static const String _columnIsChecked = 'is_checked';
static const String _columnProgress = 'progress';
static const String _columnCreatedDate = 'created_date';
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
Future<Database> _initDatabase() async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, 'tasks.db');
return await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE $_tableTasks (
$_columnId INTEGER PRIMARY KEY AUTOINCREMENT,
$_columnTask TEXT UNIQUE,
$_columnIsChecked INTEGER,
$_columnProgress REAL,
$_columnCreatedDate TEXT
)
''');
},
);
}
Future<List<Map<String, dynamic>>> getTasks() async {
final db = await database;
return await db.query(_tableTasks);
}
Future<int?> insertTask(
String task, bool isChecked, double progress, String createdDate) async {
final db = await database;
try {
return await db.insert(
_tableTasks,
{
_columnTask: task,
_columnIsChecked: isChecked ? 1 : 0,
_columnProgress: progress,
_columnCreatedDate: createdDate,
},
conflictAlgorithm: ConflictAlgorithm.ignore,
);
} catch (e) {
print("Error inserting task: $e");
return null;
}
}
Future<int> deleteTask(String task) async {
final db = await database;
return await db.delete(
_tableTasks,
where: '$_columnTask = ?',
whereArgs: [task],
);
}
Future<int> updateTask(String oldTask, String newTask, bool isChecked,
double progress, String updatedDate) async {
final db = await database;
return await db.update(
_tableTasks,
{
_columnTask: newTask,
_columnIsChecked: isChecked ? 1 : 0,
_columnProgress: progress,
_columnCreatedDate: updatedDate,
},
where: '$_columnTask = ?',
whereArgs: [oldTask],
);
}
//this is from where i stuck in further in fetchprogressDataForWeek &fetchProgressDataForPreviousWeek
Future<List<Map<String, dynamic>>> getTaskProgressForRange(
String taskName, String startDate, String endDate) async {
final db = await database;
final results = await db.query(
_tableTasks,
where: '$_columnTask = ? AND $_columnCreatedDate BETWEEN ? AND ?',
whereArgs: [taskName, startDate, endDate],
);
//final progressData = await getTaskProgressForRange(taskName, startDate, endDate);
print('Queried Data: $results');
return results;
}
Future<List<double>> fetchProgressDataForWeek(String taskName) async {
final now = DateTime.now();
final currentWeekday = now.weekday;
// Calculate the start and end of the week (Monday to Sunday)
final startOfWeek =
now.subtract(Duration(days: currentWeekday - DateTime.monday));
final endOfWeek = now.add(Duration(days: DateTime.sunday - currentWeekday));
final startDate = DateFormat('yyyy-MM-dd').format(startOfWeek);
final endDate = DateFormat('yyyy-MM-dd').format(endOfWeek);
print('Fetching data from $startDate to $endDate');
try {
final progressData =
await getTaskProgressForRange(taskName, startDate, endDate);
// Initialize result list with zeros for all 7 days
final result = List<double>.filled(7, 0.0);
// Populate result list with actual data
if (progressData.isNotEmpty) {
for (var entry in progressData) {
final dateString = entry[_columnCreatedDate] as String?;
final progressValue = entry[_columnProgress] as double?;
if (dateString != null && progressValue != null) {
final date = DateTime.parse(dateString).toLocal();
final dayOfWeek = (date.weekday - DateTime.monday + 7) %
7; // Adjust to match Monday start
if (dayOfWeek >= 0 && dayOfWeek < 7) {
result[dayOfWeek] = progressValue;
}
}
}
} else {
print('No progress data found for the specified range.');
}
// current day's bar is visible with the correct progress
final currentDayIndex = (now.weekday - DateTime.monday + 7) % 7;
if (result[currentDayIndex] == 0.0) {
result[currentDayIndex] = 1.0; // current day's bar shows 1.0 progress if the task is completed
}
print('Progress Data for the Week: $result');
return result;
} catch (e) {
print('Error fetching progress data: $e');
return List<double>.filled(7, 0.0);
}
}
Future<List<double>> fetchProgressDataForPreviousWeek(String taskName) async {
final now = DateTime.now();
final currentWeekday = now.weekday;
// current week monday
final mondayOfCurrentWeek = now.subtract(Duration(days: (currentWeekday - 1) % 7));
// Calculate Start and End of Previous Week
final startOfPreviousWeek = mondayOfCurrentWeek.subtract(Duration(days: 7));
final endOfPreviousWeek = mondayOfCurrentWeek.subtract(Duration(days: 1));
final startDate = DateFormat('yyyy-MM-dd').format(startOfPreviousWeek);
final endDate = DateFormat('yyyy-MM-dd').format(endOfPreviousWeek);
print('Monday of Current Week: $mondayOfCurrentWeek');
print('Start of Previous Week: $startOfPreviousWeek');
print('End of Previous Week: $endOfPreviousWeek');
print('Fetching data from $startDate to $endDate');
try {
final progressData =
await getTaskProgressForRange(taskName, startDate, endDate);
print('Fetched Data: $progressData');
final result = List<double>.filled(7, 0.0);
if (progressData.isNotEmpty) {
for (var entry in progressData) {
final dateString = entry[_columnCreatedDate] as String?;
final progressValue = entry[_columnProgress] as double?;
print('Date String: $dateString, Progress Value: $progressValue');
if (dateString != null && progressValue != null) {
final date = DateTime.parse(dateString).toLocal();
final dayOfWeek = (date.weekday - DateTime.monday + 7) % 7; // Adjust to match Monday start
print('Day of Week: $dayOfWeek');
if (dayOfWeek >= 0 && dayOfWeek < 7) {
result[dayOfWeek] = progressValue;
}
}
}
} else {
print('No progress data found for the specified range.');
}
print('Progress Data for the Previous Week: $result');
return result;
} catch (e) {
print('Error fetching progress data: $e');
return List<double>.filled(7, 0.0);
}
}
}
TaskController.dart
import 'package:get/get.dart';
import '../database/dbhelper.dart';
class TaskController extends GetxController {
final DatabaseHelper _databaseHelper = DatabaseHelper();
var taskList = <String>[].obs;
var isTaskSelected = <bool>[].obs;
@override
void onInit() {
super.onInit();
loadTasks();
}
void loadTasks() async {
try {
final tasks = await _databaseHelper.getTasks();
taskList.value = tasks.map((task) => task['task'] as String).toList();
isTaskSelected.value = tasks.map((task) {
final isChecked = task['is_checked'];
return (isChecked != null && (isChecked as int) == 1);
}).toList();
} catch (e) {
print("Error loading tasks: $e");
}
}
void addTask(String task) async {
try {
bool isChecked = false;
double progress = 0.0;
String createdDate = DateTime.now().toIso8601String();
int? result = await _databaseHelper.insertTask(
task, isChecked, progress, createdDate);
if (result == null) {
print("Task already exists");
} else {
print("Task added successfully");
loadTasks();
}
} catch (e) {
print("Error adding task: $e");
}
}
void deleteTask(int index) async {
try {
String taskToDelete = taskList[index];
await _databaseHelper.deleteTask(taskToDelete);
taskList.removeAt(index);
isTaskSelected.removeAt(index);
} catch (e) {
print("Error deleting task: $e");
}
}
Future<void> toggleTaskSelection(int index, bool value) async {
try {
if (index < 0 || index >= taskList.length) {
throw ArgumentError("Index out of range");
}
String taskToUpdate = taskList[index];
double progress = value ? 1.0 : 0.0;
String updatedDate = DateTime.now().toIso8601String();
isTaskSelected[index] = value;
await _databaseHelper.updateTask(
taskToUpdate, taskToUpdate, value, progress, updatedDate);
print("Task updated successfully");
} catch (e) {
print("Error toggling task selection: $e");
}
}
Future<List<double>> fetchProgressDataForWeek(String taskName) async {
try {
print("Fetching progress data for the week for task: $taskName");
final progressData =
await _databaseHelper.fetchProgressDataForWeek(taskName);
print("Fetched weekly progress data: $progressData");
return progressData;
} catch (e) {
print("Error fetching weekly progress data: $e");
return List<double>.filled(7, 0.0);
}
}
Future<List<double>> fetchProgressDataForPreviousWeek(String taskName) async {
try {
print("Fetching progress data for the previous week for task: $taskName");
final progressData =
await _databaseHelper.fetchProgressDataForPreviousWeek(taskName);
print("Fetched previous week's progress data: $progressData");
return progressData;
} catch (e) {
print("Error fetching previous week's progress data: $e");
return List<double>.filled(7, 0.0);
}
}
}
HabitScreen.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:goalsync/controller/task_controller.dart';
import 'package:goalsync/screens/task_screen.dart';
class HabitScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final TaskController taskController = Get.find();
return Scaffold(
appBar: _appBar(),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(17.0),
child: Row(
children: [],
),
),
Expanded(
child: Obx(() {
if (taskController.isTaskSelected.length < taskController.taskList.length) {
taskController.isTaskSelected.addAll(
List.generate(
taskController.taskList.length - taskController.isTaskSelected.length,
(_) => false,
),
);
}
return ListView.builder(
itemCount: taskController.taskList.length,
itemBuilder: (context, index) {
return Dismissible(
key: ValueKey(taskController.taskList[index]),
background: Container(
color: Colors.blue.shade300,
child: Icon(Icons.delete_outline, size: 35),
alignment: Alignment.centerRight,
padding: EdgeInsets.symmetric(horizontal: 20),
),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
taskController.deleteTask(index);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: GestureDetector(
onTap: () {
Get.to(TaskScreen(
taskName: taskController.taskList[index],
isTaskChecked: taskController.isTaskSelected[index],
));
},
child: Container(
margin: EdgeInsets.symmetric(vertical: 5.0),
padding: EdgeInsets.all(6.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.black54, width: 2),
),
child: Row(
children: [
Expanded(
child: Text(
taskController.taskList[index],
style: TextStyle(color: Colors.black, fontSize: 18),
),
),
Checkbox(
value: taskController.isTaskSelected[index],
onChanged: (bool? value) {
if (value != null) {
taskController.toggleTaskSelection(index, value);
}
},
activeColor: Colors.blue,
checkColor: Colors.white,
),
],
),
),
),
),
);
},
);
}),
)
],
),
);
}
AppBar _appBar() {
return AppBar(
title: Text("Habit Screen"),
backgroundColor: Colors.blue,
actions: [
TextButton(
onPressed: () {
_showAddTaskDialog();
},
child: Text(
'Add Task',
style: TextStyle(
color: Colors.black,
fontSize: 16,
),
),
),
],
);
}
void _showAddTaskDialog() {
final TaskController taskController = Get.find();
final TextEditingController taskControllerInput = TextEditingController();
showDialog(
context: Get.context!,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Add New Task'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: taskControllerInput,
decoration: InputDecoration(hintText: 'Enter task'),
autofocus: true,
),
],
),
actions: [
TextButton(
onPressed: () {
final task = taskControllerInput.text.trim();
if (task.isNotEmpty) {
taskController.addTask(task);
}
Get.back(); // Close the dialog
},
child: Text('Save'),
),
TextButton(
onPressed: () {
Get.back(); // Close the dialog
},
child: Text('Cancel'),
),
],
);
},
);
}
}
9