Recognizing widgets on the Flutter app screen

Hola, Amigos! In touch Sasha Chaplygin, Flutter-dev of the Amiga product development agency and co-author of the telegram channel Flutter. A lot of. Today we will practice again! I’ll tell you about an interesting topic – determining the position of an object on the screen. This can be useful when we want to understand whether a particular widget is currently visible on the screen or not.

Let's look at a specific example: an application has a block with notifications, and according to the technical specifications, it is necessary to send a request to the server when a tile in the notification list becomes visible on the screen in order to mark the notification as read.

How to do this?

First, let's create a custom notification widget. To define an object on the screen we use the package visibility_detector. When using this package, be sure to use UniqueKey, since this package includes RenderObject.

import 'package:flutter/material.dart';
import 'package:hmelbakery/core/style/colors.dart';
import 'package:visibility_detector/visibility_detector.dart';


class NotificationChip extends StatefulWidget {
 NotificationChip({
   required this.index,
   required this.visibleIndex,
   super.key,
   this.title="",
   this.text="",
   this.subtitle="",
   this.status = true,
 });


 final String title;
 final int index;
 final ValueChanged<int> visibleIndex;
 final String text;
 final String subtitle;
 final bool status;


 @override
 State<NotificationChip> createState() => _NotificationChipState();
}


class _NotificationChipState extends State<NotificationChip> {
 final UniqueKey key = UniqueKey();
 late bool _visible;


 @override
 void initState() {
   _visible = widget.status;
   super.initState();
 }


 @override
 Widget build(BuildContext context) {
   return VisibilityDetector(
     onVisibilityChanged: !_visible
         ? (visibilityInfo) {
             // Здесь определяется на сколько виден объект в процентах
             var visiblePercentage = visibilityInfo.visibleFraction * 100;
             if (visiblePercentage == 100.0) {
               setState(() {
                 _visible = true;
               });
               widget.visibleIndex.call(widget.index); // прочитали
             }
           }
         : null,
     key: key,
     child: Container(
       decoration: BoxDecoration(color: AppColors.grey, borderRadius: BorderRadius.circular(16)),
       child: Padding(
         padding: const EdgeInsets.all(16),
         child: Column(
           mainAxisAlignment: MainAxisAlignment.start,
           crossAxisAlignment: CrossAxisAlignment.start,
           children: [
             Row(
               mainAxisAlignment: MainAxisAlignment.spaceBetween,
               children: [
                 Text(
                   widget.title,
                   style: Theme.of(context).textTheme.titleLarge,
                 ),
                 if (!_visible)
                   Container(
                     width: 16,
                     height: 16,
                     decoration: const BoxDecoration(
                       color: AppColors.yellow,
                       shape: BoxShape.circle,
                     ),
                   ),
               ],
             ),
             const SizedBox(height: 8),
             Text(widget.text, style: Theme.of(context).textTheme.bodyMedium),
             const SizedBox(height: 16),
             Text(
               widget.subtitle,
               style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: AppColors.black400),
             ),
           ],
         ),
       ),
     ),
   );
 }
}

Display a list of notifications on the screen.

ListView.builder(
   padding: const EdgeInsets.symmetric(horizontal: 16),
   itemBuilder: (BuildContext context, int index) {
     if (index == state.pageState.data.length - 1 && !state.pageState.loadNewPage) {
       context.read<ProfileNotificationsBloc>().add(ProfileNotificationsFetchDataEvent());
     }
     return Column(
       children: [
         NotificationChip(
           title: state.pageState.data[index].title,
           text: state.pageState.data[index].text,
           subtitle: DateConverter.formattingDateWTime(state.pageState.data[index].datetime),
           status: !(state.pageState.data[index].statusCode == 'unread'),
           index: index,
           visibleIndex: (int value) {
             context
                 .read<ProfileNotificationsBloc>()
                 .add(ProfileNotificationsMarkReadEvent(index: value)); // передаем в блок индекс прочитаного 
           },
         ),
         const SizedBox(height: 8),
         if (index == state.pageState.data.length - 1 && state.pageState.loadNewPage) ...[
           const Center(child: CircularProgressIndicator(color: AppColors.black)),
           const SizedBox(height: 20),
         ],
       ],
     );
   },
   itemCount: state.pageState.data.length,
 ),

In the block, we create a list of read indexes and immediately change the state of those that have already been viewed to remove the “read” icon.

markRead(ProfileNotificationsMarkReadEvent event, emit) async {
 if (!state.pageState.readIndexes.contains(event.index)) {
   emit(ProfileNotificationsUp(state.pageState.copyWith(
     readIndexes: [...state.pageState.readIndexes, event.index],
     data: state.pageState.data..[event.index] = state.pageState.data[event.index].copyWith(statusCode: 'read'),
   )));
 }
}

Next, you need to solve the problem of how to correctly send information to the server? We won’t send every read index, right?

Timer? _timer;
List<int> _tempList = [];


_timerFunc() { // старт функции в блоке
 _timer = Timer.periodic(
   const Duration(seconds: 1),
   (timer) {
     if (_tempList.length < state.pageState.readIndexes.length) {
       _tempList = state.pageState.readIndexes;
       notificationsRepository.markRead(
         request: MarkReadNotificationRequest(
           notifications: state.pageState.data
               .sublist(_tempList.first, _tempList.length > 1 ? _tempList.last : _tempList.first + 1)
               .map((e) => e.id)
               .toList(),
         ),
       );
     }
   },
 );
}

We create a timer that checks every second whether the list of read notifications has changed, and then sends it to the server. Of course, we will not force the user to wait for a response and block the screen by loading, so we do not use async/await.

All is ready! I hope you find it useful. Share in our chat mobile developers about their experience using the visibility_detector package.

We are also always waiting for you in our telegram channel Flutter. A lot of, which we run as a team of mobile developers. We talk about our personal experience and share tips from soft skills to technical knowledge. Join us!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *