작성
·
76
0
import 'dart:async';
import 'dart:convert';
import 'package:carwash/common/theme/theme.dart';
import 'package:carwash/community/view/community_screen_bak.dart';
import 'package:carwash/firebase/provider/pushRoute_provider.dart';
import 'package:carwash/user/view/splash_screen.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:get/get.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:kakao_flutter_sdk_user/kakao_flutter_sdk_user.dart';
import 'common/provider/go_router.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
//! Notification을 위한 StreamController 전역 변수 선언
String pushRoute = '';
final streamController = StreamController<String>();
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
print("백그라운드 메시지 처리.. }
void initializeNotification() async {
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(const AndroidNotificationChannel(
'high_importance_channel', 'high_importance_notification',
importance: Importance.max));
DarwinInitializationSettings iosInitializationSettings =
const DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true
);
// IOSInitializationSettings iosInitializationSettings =
// IOSInitializationSettings(
// requestAlertPermission: true,
// requestBadgePermission: true,
// requestSoundPermission: true
// );
await flutterLocalNotificationsPlugin.initialize(
InitializationSettings(
android: AndroidInitializationSettings("@mipmap/ic_launcher"),
iOS: iosInitializationSettings
),
//앱이 켜져있을때 푸쉬 알림 클릭시 해당 경로로 이동 설정
onDidReceiveNotificationResponse: (NotificationResponse details) async {
print('onDidReceiveNotificationResponse - payload: ${details.payload}');
final payload = details.payload ?? '';
final parsedJson = jsonDecode(jsonEncode(payload));
if(payload!=''){
pushRoute = payload;
streamController.add(payload);
}
}
);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
}
void main() async{
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
await WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await initializeDateFormatting();
KakaoSdk.init(nativeAppKey: '7d131476e9dd71890bf99ffa94dec12d'); // 이 줄을 runApp 위에 추가한다.
//푸쉬알림설정
initializeNotification();
runApp(
const ProviderScope(child: _App())
);
}
class _App extends ConsumerWidget {
const _App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context,WidgetRef ref) {
final router = ref.watch(routerProvider);
return MaterialApp.router(
// 시스템 설정에 따른 Theme
// themeMode: ThemeMode.system,
theme: TAppTheme.lightTheme,
// darkTheme: TAppTheme.darkTheme,
debugShowCheckedModeBanner: false,
routerDelegate: router.routerDelegate,
routeInformationParser: router.routeInformationParser,
routeInformationProvider: router.routeInformationProvider,
);
}
}
위는 제 메인 함수입니다.
onDidReceiveNotification을 통해 넘겨온 정보를 streamcontroller에 저장시켜주고
import 'dart:io';
import 'package:carwash/car/view/carwash_record_screen.dart';
import 'package:carwash/common/layout/default_layout_v2.dart';
import 'package:carwash/common/provider/go_router.dart';
import 'package:carwash/common/view/main/main_screen.dart';
import 'package:carwash/community/view/community_screen.dart';
import 'package:carwash/firebase/provider/pushRoute_provider.dart';
import 'package:carwash/user/provider/user_me_provider.dart';
import 'package:carwash/user/view/profile/profile_screen.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:iconsax/iconsax.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../community/view/community_detail_screen.dart';
import '../../main.dart';
import '../const/colors.dart';
class RootTab extends ConsumerStatefulWidget {
static String get routeName => 'home';
const RootTab({Key? key}) : super(key: key);
@override
ConsumerState<RootTab> createState() => _RootTabState();
}
class _RootTabState extends ConsumerState<RootTab> with SingleTickerProviderStateMixin {
late TabController _tabController;
int _selectIndex = 0;
int _nowscreen = 0;
var messageString = "";
@override
Widget build(BuildContext context) {
Stream route = streamController.stream;
if(route!=null){
print(route);
print('푸쉬 경로');
}
return StreamBuilder(
stream: route,
builder: (BuildContext context,AsyncSnapshot snapshot){
print(snapshot.data);
if(snapshot.data!=null){
Navigator.pop(context);
}
return DefaultLayoutV2(
bottomNavagtionBar: Theme(
data: ThemeData(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
selectedItemColor: PRIMARY_COLOR,
unselectedItemColor: Colors.black,
elevation: 0,
backgroundColor: Colors.white,
onTap: (int index) {
_tabController.animateTo(index);
},
currentIndex: _selectIndex,
items: const [
BottomNavigationBarItem(icon: Icon(Iconsax.home), label: '홈'),
BottomNavigationBarItem(icon: Icon(Iconsax.message_edit), label: '세차기록'),
BottomNavigationBarItem(icon: Icon(Iconsax.messages_3), label: '커뮤니티'),
BottomNavigationBarItem(icon: Icon(Iconsax.user), label: '내정보'),
],
),
),
child: DefaultTabController(
length: 4,
child: TabBarView(
physics: const NeverScrollableScrollPhysics(),
controller: _tabController,
children: [
MainScreen(tabController: changeTab),
CarWashRecordScreen(),
CommunityScreen(nowscreen: _nowscreen),
UserProfileScreen(),
],
),
),
);}
);
}
}
//각종 권한 요청
Future<void> _requestPermission() async {
if (Platform.isIOS) {
// iOS에서 알림 권한 요청 코드
final IOSFlutterLocalNotificationsPlugin? iosImplementation = FlutterLocalNotificationsPlugin().resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>();
if (iosImplementation != null) {
bool? result = await iosImplementation.requestPermissions(
alert: true,
badge: true,
sound: true,
);
if (result != null && result) {
print('Notification permission granted.');
} else {
print('Notification permission denied.');
}
}
} else if (Platform.isAndroid) {
await Permission.notification.request();
// Android에서 알림 권한 요청 코드
PermissionStatus status = await Permission.notification.status;
if (status.isGranted) {
// 권한이 허용되었을 때 추가 작업 수행
} else if (status.isDenied || status.isPermanentlyDenied) {
// 권한이 거부되었거나 영구적으로 거부된 경우
} else {
await Permission.notification.request();
}
}
}
메인화면에서 위와 같이 streambuilder를 통해 메인화면을 호출하고 있습니다.
print(snapshot.data);
if(snapshot.data!=null){
Navigator.pop(context);
}
위의 부분이 푸쉬알림을 클릭했을때 streamController에 값이 넘어가 들어오는 부분인데 저 코드를 실행시키면 항상 setState() or markNeedsBuild() called during build. 과 같은 에러가 뜹니다 언제 화면을 push해줘야할까요
답변 1
0
안녕하세요, 인프런 AI 인턴입니다.
StreamBuilder
를 사용하는 경우, setState()
같은 상태 업데이트는 build
메서드가 호출되고 있을 때 하지 않는 것이 중요합니다. 이는 build
메서드가 상태 변경에 반응하여 UI를 재구성하는 동안에 상태 업데이트가 발생하면 오류가 발생할 수 있기 때문입니다.
Navigator.pop(context)
를 사용하여 화면을 닫는 동작 역시 build
메서드 내에서 이루어져 화면에 이상이 생길 수 있으므로, 아래와 같이 WidgetsBinding.instance.addPostFrameCallback
를 활용하여 다음 프레임에서 네비게이션 동작을 수행하도록 지연시킬 수 있습니다.
if (snapshot.data != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pop(context);
});
}
이 코드는 build
메서드가 완료된 후에 Navigator.pop(context)
가 실행되도록 합니다. 이를 통해 화면을 안전하게 닫을 수 있습니다.
부가적인 도움이 필요하거나 질문이 있으시면, 언제든지 추가로 물어봐 주세요! 😊
참고 URL: [https://docs.flutter.dev/development/ui/interactive#functional-programming-techniques]