강의

멘토링

로드맵

인프런 커뮤니티 질문&답변

dlckdals9467님의 프로필 이미지
dlckdals9467

작성한 질문수

플러터 Streambuilder질문

작성

·

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]

dlckdals9467님의 프로필 이미지
dlckdals9467

작성한 질문수

질문하기