Распознавание виджетов на экране приложения Flutter

Hola, Amigos! На связи Саша Чаплыгин, Flutter-dev агентства продуктовой разработки Amiga и соавтор телеграм-канала Flutter. Много. Сегодня мы вновь займемся практикой! Расскажу об интересной теме — определение положения объекта на экране. Это может быть полезно, когда мы хотим понять, виден тот или иной виджет на экране в данный момент или нет.

Давайте рассмотрим конкретный пример: в приложении есть блок с уведомлениями, и согласно техническому заданию, необходимо отправить запрос на сервер, когда плитка в списке уведомлений становится видимой на экране, чтобы пометить уведомление как прочитанное.

Notifications read

Как же это сделать?

Для начала создадим кастомный виджет уведомления. Для определения объекта на экране используем пакет visibility_detector. При применении данного пакета обязательно используйте UniqueKey, так как данный пакет включает в себя 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 visibleIndex;
final String text;
final String subtitle;
final bool status;
@override
State createState() => _NotificationChipState();
}
class _NotificationChipState extends State {
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),
),
],
),
),
),
);
}
}

В блоке создаем список прочитанных индексов и тут же меняем состояние тех, что уже просмотрели, для снятия значка «прочитанности».

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'),
)));
}
}

Далее нужно решить задачу, как правильно отправить на сервер информацию? Не будем же мы каждый прочитанный индекс отправлять, верно?

Timer? _timer;
List _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(),
),
);
}
},
);

Создаем таймер, который каждую секунду проверяет изменился ли список прочитанных уведомлений, и тогда отправляем на сервер. Конечно же мы не будем заставлять пользователя ждать ответа и блокировать экран загрузкой, поэтому не используем async/await.

Всё готово! Надеюсь, вам будет полезно. Делитесь в нашем чате мобильных разработчиков о своем опыте применения пакета visibility_detector.

А также всегда ждем вас в нашем телеграм-канале Flutter. Много, который мы ведем командой мобильных разработчиков. Рассказываем про свой личный опыт и делимся советами от софт-скиллов до технических знаний. Присоединяйтесь!

Хотите связаться с владельцем
компании напрямую?
Дмитрий Тарасов
Дмитрий Тарасов
СЕО

НАПИСАТЬ