Glassy homescreen widget flutter | to do app home screen widget using flutter | Complete Code







In this blog we will see how we can create homescreen widget in our flutter app.If you have tried creating widget then you may know the home_widget package in flutter however we won't be using it to create our widget.i have tried implementing home_widget but i got tons of error and i have even no idea for some error.everytime i solved one error, new error would popup and it was frustrating so i decided to do it without home_widget package.

in this project we will use only one flutter package "shared_preferences".

as we aren't using package for creating widget so we have to write a lot of code.let's get started with sample applicatoin "to do app".

1. Create flutter Project

flutter create widgetnoteapp

    add "shared_preferences" packages on pubspec.yml file



2.Project Structure

below is our project structure containing all files and folders and we have to create 2 new folder 

go to path widgetnoteapp\android\app\src\main\res create layout and xml folder

create all files from below project tree


widgetnoteapp/ ├── android/ │ └── app/ │ └── src/ │ └── main/ │ ├── kotlin/ │ │ └── com/ │ │ └── example/ │ │ └── widgetnoteapp/ │ │ ├── MainActivity.kt │ │ ├── TaskWidgetProvider.kt │ │ ├── TaskWidgetService.kt │ │ └── TaskRemoteViewsFactory.kt │ ├── res/ │ │ ├── layout/ │ │ │ ├── widget_layout.xml │ │ │ ├── card_layout.xml │ │ │ └── task_layout.xml │ │ ├── drawable/ │ │ │ ├── glassy_task_background.xml │ │ │ ├── glassy_widget_background.xml │ │ │ ├── launch_background.xml │ │ │ ├── task_card_background.xml │ │ │ └── card_background.xml │ │ ├── xml/ │ │ │ └── task_widget_info.xml │ ├── AndroidManifest.xml ├── lib/ │ ├── main.dart ├── pubspec.yaml



3.Complete Code 

while this project was created the version of flutter was 3.24.3. let's jump to coding part


 AndroidManifest.xml

<application

<!-- add reciever and services for widget -->
        <receiver
        android:name=".TaskWidgetProvider"
        android:label="Task Widget"
        android:exported="true">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            <action android:name="com.example.widgetnoteapp.UPDATE_WIDGET" />
        </intent-filter>
   
        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/task_widget_info" />
    </receiver>
   
    <service android:name=".TaskWidgetService"
        android:permission="android.permission.BIND_REMOTEVIEWS" />

        <!-- end of reciever and services for widget -->
</application>




 widgetnoteapp\android\app\src\main\kotlin\com\example\widgetnoteapp 


TaskWidgetProvider.kt

package com.example.widgetnoteapp

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.util.Log
import android.widget.RemoteViews

class TaskWidgetProvider : AppWidgetProvider() {
    private val TAG = "TaskWidgetProvider"

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        Log.d(TAG, "onUpdate called with appWidgetIds: ${appWidgetIds.joinToString()}")
        for (appWidgetId in appWidgetIds) {
            Log.d(TAG, "Updating widget with ID: $appWidgetId")
            updateWidget(context, appWidgetManager, appWidgetId)
        }
    }

    private fun updateWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
        Log.d(TAG, "updateWidget called for widget ID: $appWidgetId")

        val views = RemoteViews(context.packageName, R.layout.widget_layout)

        // Set up the intent for the service
        val serviceIntent = Intent(context, TaskWidgetService::class.java)
        views.setRemoteAdapter(R.id.task_list_view, serviceIntent)

        // Set empty view
        views.setEmptyView(R.id.task_list_view, R.id.empty_view)

        // Set up the pending intent template for item clicks
        val clickIntent = Intent(context, MainActivity::class.java)
        val clickPendingIntent = PendingIntent.getActivity(
            context,
            0,
            clickIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        views.setPendingIntentTemplate(R.id.task_list_view, clickPendingIntent)

        // Update the widget
        appWidgetManager.updateAppWidget(appWidgetId, views)
        Log.d(TAG, "Widget updated successfully for ID: $appWidgetId")
    }

    override fun onReceive(context: Context?, intent: Intent?) {
        Log.d(TAG, "onReceive called with action: ${intent?.action}")
        super.onReceive(context, intent)

        if (intent?.action == "com.example.widgetnoteapp.UPDATE_WIDGET") {
            Log.d(TAG, "Received custom action to update widget")
            val appWidgetManager = AppWidgetManager.getInstance(context)
            val componentName = android.content.ComponentName(context!!, TaskWidgetProvider::class.java)
            val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName)

            Log.d(TAG, "Found widget IDs to update: ${appWidgetIds.joinToString()}")
            for (appWidgetId in appWidgetIds) {
                updateWidget(context, appWidgetManager, appWidgetId)
            }

            // Notify that the data changed
            appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.task_list_view)
        } else {
            Log.d(TAG, "No custom action matched for intent")
        }
    }
}



TaskWidgetService.kt

package com.example.widgetnoteapp

import android.content.Intent
import android.widget.RemoteViewsService

class TaskWidgetService : RemoteViewsService() {
    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
        return TaskRemoteViewsFactory(applicationContext)
    }
}




MainActivity.kt

package com.example.widgetnoteapp

import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.appwidget.AppWidgetManager
import android.content.ComponentName

class MainActivity: FlutterActivity() {
    private val CHANNEL = "com.example.widgetnoteapp/update_widget"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "updateWidget") {
                //log
                // Update your widget here
                updateWidget()
                result.success(null)
            } else {
                result.notImplemented()
            }
        }
    }

    private fun updateWidget() {
        // Update widget data
        val appWidgetManager = AppWidgetManager.getInstance(applicationContext)
        val ids = appWidgetManager.getAppWidgetIds(
            ComponentName(applicationContext, TaskWidgetProvider::class.java)
        )
       
        // This is crucial - it triggers onDataSetChanged() in RemoteViewsFactory
        appWidgetManager.notifyAppWidgetViewDataChanged(ids, R.id.task_list_view)
       
        // Send broadcast to update widget
        val intent = Intent(applicationContext, TaskWidgetProvider::class.java)
        intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
        sendBroadcast(intent)
    }
}




TaskRemoteViewsFactory.kt

package com.example.widgetnoteapp

import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import android.widget.RemoteViews
import android.widget.RemoteViewsService

class TaskRemoteViewsFactory(private val context: Context) : RemoteViewsService.RemoteViewsFactory {
    private var tasks: List<String> = listOf()
    private val TAG = "TaskRemoteViewsFactory"

    override fun onCreate() {
        Log.d(TAG, "onCreate")
        loadTasks()
    }

    override fun onDataSetChanged() {
        Log.d(TAG, "onDataSetChanged called")
        loadTasks()
        Log.d(TAG, "Loaded ${tasks.size} tasks")
    }

    private fun loadTasks() {
        val prefs: SharedPreferences = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
        val tasksString = prefs.getString("flutter.tasks", "")
        Log.d(TAG, "Loading tasks from prefs: $tasksString")
        tasks = tasksString?.split(",")?.filter { it.isNotEmpty() } ?: emptyList()
    }

    override fun onDestroy() {
        // No cleanup needed
    }

    override fun getCount(): Int = tasks.size

    override fun getViewAt(position: Int): RemoteViews {
        return RemoteViews(context.packageName, R.layout.task_item).apply {
            setTextViewText(R.id.task_text, tasks[position])
        }
    }

    override fun getLoadingView(): RemoteViews? = null

    override fun getViewTypeCount(): Int = 1

    override fun getItemId(position: Int): Long = position.toLong()

    override fun hasStableIds(): Boolean = true
}



 widgetnoteapp\android\app\src\main\res\drawable\ 


card_background.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="8dp" />
    <solid android:color="#000000" />
    <padding
        android:left="8dp"
        android:top="8dp"
        android:right="8dp"
        android:bottom="8dp" />
</shape>


glassy_task_background.xml

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <corners android:radius="12dp"/>
            <gradient
                android:startColor="#66FFFFFF"
                android:endColor="#40FFFFFF"
                android:angle="45"/>
        </shape>
    </item>
    <item android:top="1dp" android:left="1dp" android:right="1dp" android:bottom="1dp">
        <shape android:shape="rectangle">
            <corners android:radius="12dp"/>
            <gradient
                android:startColor="#26FFFFFF"
                android:endColor="#0DFFFFFF"
                android:angle="45"/>
            <stroke
                android:width="0.5dp"
                android:color="#33FFFFFF"/>
        </shape>
    </item>
</layer-list>



glassy_widget_background.xmlndd.xnml

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <corners android:radius="16dp"/>
            <gradient
                android:startColor="#80000000"
                android:endColor="#66000000"
                android:angle="45"/>
        </shape>
    </item>
    <item android:top="1dp" android:left="1dp" android:right="1dp" android:bottom="1dp">
        <shape android:shape="rectangle">
            <corners android:radius="16dp"/>
            <gradient
                android:startColor="#33FFFFFF"
                android:endColor="#1AFFFFFF"
                android:angle="45"/>
        </shape>
    </item>
</layer-list>



launch_background.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@android:color/white" />

    <!-- You can insert your own image assets here -->
    <!-- <item>
        <bitmap
            android:gravity="center"
            android:src="@mipmap/launch_image" />
    </item> -->
</layer-list>





task_card_background.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="8dp"/>
    <solid android:color="#454545"/>
    <stroke
        android:width="1dp"
        android:color="#555555"/>
</shape>




 widgetnoteapp\android\app\src\main\res\layout\ 


card_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/card_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp"
    android:background="@drawable/card_background"
    android:layout_marginBottom="8dp"
    android:elevation="2dp">

    <TextView
        android:id="@+id/task_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:textColor="@android:color/black" />
</LinearLayout>





task_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/glassy_task_background"
    android:padding="12dp"
    android:layout_marginHorizontal="4dp">
   
    <TextView
        android:id="@+id/task_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:textColor="#FFFFFF"
        android:alpha="0.95"/>
</LinearLayout>



widget_layout.xml

RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="12dp"
    android:background="@drawable/glassy_widget_background">
   
    <TextView
        android:id="@+id/widget_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tasks"
        android:textSize="20sp"
        android:textStyle="bold"
        android:textColor="#FFFFFF"
        android:alpha="0.95"
        android:layout_marginBottom="8dp"
        android:paddingStart="4dp"/>
   
    <ListView
        android:id="@+id/task_list_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/widget_title"
        android:divider="@null"
        android:dividerHeight="8dp"/>
   
    <TextView
        android:id="@+id/empty_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/widget_title"
        android:gravity="center"
        android:text="No tasks available"
        android:textColor="#E6FFFFFF"
        android:padding="16dp"
        android:visibility="gone"/>
</RelativeLayout>



 widgetnoteapp\android\app\src\main\res\xml\ 



task_widget_info.xml

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="250dp"
    android:minHeight="100dp"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/widget_layout"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen" />




 widgetnoteapp\lib\ 



main.dart

      import 'package:flutter/material.dart';
      import 'package:shared_preferences/shared_preferences.dart';
      import 'package:flutter/services.dart';

      const platform = MethodChannel('com.example.widgetnoteapp/update_widget');

      void main() {
        runApp(MyApp());
      }

      class MyApp extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return MaterialApp(
            title: 'To-Do Widget App',
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),
            home: TaskInputScreen(),
          );
        }
      }

      class TaskInputScreen extends StatefulWidget {
        @override
        _TaskInputScreenState createState() => _TaskInputScreenState();
      }

      class _TaskInputScreenState extends State<TaskInputScreen> {
        final TextEditingController _taskController = TextEditingController();
        String _tasks = ''; // Use a single string to store tasks
        //method to update the widget
        Future<void> _updateAndroidWidget() async {
          try {
            // First save data using SharedPreferences
            SharedPreferences prefs = await SharedPreferences.getInstance();
            await prefs.setString('tasks', _tasks);

            // Then notify the platform to update the widget
            await platform.invokeMethod('updateWidget');
          } catch (e) {
            print('Error updating widget: $e');
          }
        }

        void _saveTask() async {
          String taskTitle = _taskController.text;
          if (taskTitle.isNotEmpty) {
            // Append the new task to the existing tasks
            setState(() {
              _tasks = _tasks.isEmpty ? taskTitle : '$_tasks,$taskTitle';
            });
            SharedPreferences prefs = await SharedPreferences.getInstance();
            await prefs.setString('tasks', _tasks); // Make sure the key matches

            // Update the widget
            await _updateAndroidWidget();

            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('Task added!')),
            );

            _taskController.clear();
          }
        }

        void _loadSavedTasks() async {
          SharedPreferences prefs = await SharedPreferences.getInstance();
          String tasksString = prefs.getString('tasks') ?? '';
          if (tasksString.isNotEmpty) {
            setState(() {
              _tasks = tasksString;
            });
            // Update widget when tasks are loaded
            await _updateAndroidWidget();
          }
        }

        void _removeTask(int index) async {
          List<String> taskList = _tasks.split(',');
          if (index < taskList.length) {
            taskList.removeAt(index);
            setState(() {
              _tasks = taskList.join(',');
            });

            // Update the widget
            await _updateAndroidWidget();
          }
        }

        @override
        void initState() {
          super.initState();
          _loadSavedTasks();
        }

        @override
        Widget build(BuildContext context) {
          List<String> displayedTasks = _tasks.isNotEmpty
              ? _tasks.split(',')
              : []; // Create a list from the tasks string

          return Scaffold(
            appBar: AppBar(
              title: const Text('To-Do List'),
            ),
            body: ListView.builder(
              itemCount: displayedTasks.length,
              itemBuilder: (context, index) {
                return Dismissible(
                  key: Key(displayedTasks[index]),
                  background: Container(
                    color: Colors.red,
                    alignment: Alignment.centerRight,
                    padding: const EdgeInsets.symmetric(horizontal: 20),
                    child: const Icon(Icons.delete, color: Colors.white),
                  ),
                  onDismissed: (direction) {
                    _removeTask(index);
                    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
                      content: Text('Task removed!'),
                    ));
                  },
                  child: Card(
                    margin: const EdgeInsets.all(10),
                    elevation: 5,
                    child: ListTile(
                      title: Text(
                        displayedTasks[index],
                        style: const TextStyle(fontSize: 18),
                      ),
                    ),
                  ),
                );
              },
            ),
            floatingActionButton: FloatingActionButton(
              child: const Icon(Icons.add),
              onPressed: () {
                showDialog(
                  context: context,
                  builder: (context) {
                    return AlertDialog(
                      title: const Text('Add Task'),
                      content: TextField(
                        controller: _taskController,
                        decoration: const InputDecoration(hintText: 'Enter task'),
                      ),
                      actions: <Widget>[
                        TextButton(
                          child: const Text('Add'),
                          onPressed: () {
                            _saveTask();
                            Navigator.of(context).pop();
                          },
                        ),
                      ],
                    );
                  },
                );
              },
            ),
          );
        }
      }





4. Run Project

flutter run

if everything was done correctly then applicatoin will work.you can add and remove tasks and use widget on homescreen.


Post a Comment

Previous Post Next Post