스마트시대

22 RIVERPOD 22.1 NotifierProvider 22.2 ConsumerWidget 22.3 recap for ConsumerWidget 22.4 AsyncNotifierProvider 본문

Programing/Flutter

22 RIVERPOD 22.1 NotifierProvider 22.2 ConsumerWidget 22.3 recap for ConsumerWidget 22.4 AsyncNotifierProvider

스마트시대 2023. 5. 28. 22:07
728x90

 

 

22.1 NotifierProvider

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:tiktok_clone/features/videos/models/playback_config_model.dart';
import 'package:tiktok_clone/features/videos/repos/playback_config_repo.dart';

//(21.3)
// class PlaybackConfigViewModel extends ChangeNotifier {
//(22.1)
class PlaybackConfigViewModel extends Notifier<PlaybackConfigModel> {
  final PlaybackConfigRepository _repository;

  // late final PlaybackConfigModel _model = PlaybackConfigModel(
  //   muted: _repository.isMuted(),
  //   autoplay: _repository.isAutoplay(),
  // );

  PlaybackConfigViewModel(this._repository);

  // bool get muted => _model.muted;

  // bool get autoplay => _model.autoplay;

  void setMuted(bool value) {
    _repository.setMuted(value); // repository에서 값을 디스크에 persist(지속)하게 저장
    // _model.muted = value; //model 수정
    // notifyListeners(); //listen하고 있는 모두에게 notify해줌(21.3)
    //(22.1)riverpod에서는 _model을 mutated(바꿔오다)할 수없고 완전 새로운 state를 만들어야함(refactor).
    state = PlaybackConfigModel(
      muted: value,
      autoplay: state.autoplay,
    );
  }

  void setAutoplay(bool value) {
    _repository.setAutoplay(value);
    // _model.autoplay = value;
    // notifyListeners();(21.3)
    //(22.1)riverpod에서는 _model을 mutated(바꿔오다)할 수없고 완전 새로운 state를 만들어야함(refactor).
    state = PlaybackConfigModel(
      muted: state.muted,
      autoplay: value,
    );
  }

  //(22.1)initialized data
  @override
  PlaybackConfigModel build() {
    return PlaybackConfigModel(
      muted: _repository.isMuted(),
      autoplay: _repository.isAutoplay(),
    );
  }
}

final playbackConfigProvider =
    NotifierProvider<PlaybackConfigViewModel, PlaybackConfigModel>(
  //(22.1)여기서만 하니까 일단 무시
  () => throw UnimplementedError(),
);

 

 22.2 ConsumerWidget

// class SettingsScreen extends StatefulWidget {

-------------------
//(22.2에서 stateless위젯으로 바꾸고 ConsumerWidget로 다시 바꾸기)
class SettingsScreen extends ConsumerWidget {
-------------------


  const SettingsScreen({super.key});
  
  
  
  @override
  -------------------
  //(22.2)
  Widget build(BuildContext context, WidgetRef ref) {
  -------------------
  
  
    // 언어 재설정 안해도 되는 위젯
    return Localizations.override(
      context: context,
      
      
      
      
      
            SwitchListTile.adaptive(
            
            
              -------------------
              //(22.2)
              value: ref.watch(playbackConfigProvider).muted,
              onChanged: (value) =>
                  ref.read(playbackConfigProvider.notifier).setMuted(value),
  -------------------
  
  
  
              title: const Text("Auto Mute video"),
              subtitle: const Text("Videos will be muted by default."),
            ),
            SwitchListTile.adaptive(
            
            
              -------------------
              //(22.2)
              value: ref.watch(playbackConfigProvider).autoplay,
              onChanged: (value) =>
                  ref.read(playbackConfigProvider.notifier).setAutoplay(value),
  -------------------
  
  
  
              title: const Text("Autoplay"),
              subtitle: const Text("Videos will start playing automatically."),
            ),

            Checkbox(
            
            
              -------------------
              //(22.2)
              // value: _notifications,
              // onChanged: _onNotificationsChaged,
  -------------------
  
  
              value: false,
              onChanged: (value) {},
            ),
            CheckboxListTile(
              activeColor: Colors.black,
              
                -------------------
              //(22.2)
              // value: _notifications,
              // onChanged: _onNotificationsChaged,
  -------------------
  
  
              value: false,
              onChanged: (value) {},
              title: const Text("Enable notifications"),
            ),
            ListTile(
              onTap: () async {
                final date = await showDatePicker(
                  context: context,
                  initialDate: DateTime.now(),
                  firstDate: DateTime(1980),
                  lastDate: DateTime(2030),
                );
                if (kDebugMode) {
                  print(date);
                }
                
                  -------------------
                //(22.2)
                // if (!mounted) return;
                  -------------------
                  
                  
                final time = await showTimePicker(
                  context: context,
                  initialTime: TimeOfDay.now(),
                );
                if (kDebugMode) {
                  print(time);
                }
                
                  -------------------
                //(22.2)
                // if (!mounted) return;
                  -------------------
                  
                  
                  
                final booking = await showDateRangePicker(
                  context: context,
                  firstDate: DateTime(1980),
                  lastDate: DateTime(2030),
                  builder: (context, child) {
                    return Theme(
                      data: ThemeData(
                        appBarTheme: const AppBarTheme(
                          foregroundColor: Colors.white,
                          backgroundColor: Colors.black,

밑에 두 버튼 누르면 비디오가 재생되지 않는 슬라이드가 넘어가는 걸 볼 수 있다.

 

 

22.3 recap for ConsumerWidget

-----------------
//(22.3)
class VideoPost extends ConsumerStatefulWidget {
-----------------

  //onVideoFinished 처리해주기 위한 property,statefulWideget은 여기 있는데 작업은 state에서 해줄 필요
  final Function onVideoFinished;
  
  
      required this.index,
  });

  @override
  // State<VideoPost> createState() => _VideoPostState();
  
  -----------------
  //(22.3)
  VideoPostState createState() => VideoPostState();
  -----------------
}


  -----------------
//(22.3)
class VideoPostState extends ConsumerState<VideoPost>
  -----------------
  
  
    with SingleTickerProviderStateMixin {
  //VideoPlayerController이용하기 위한 함수
  late final VideoPlayerController _videoPlayerController;

  //animation을 커스터마이즈하는 함수
  final Duration _animationDuration = const Duration(milliseconds: 200);
  late final AnimationController _animationController;


  -----------------
  //(22.3)
  bool _isPaused = false;
  final bool _isMuted = false;
    -----------------
    
    
    
        super.dispose();
  }

  //(21.4)볼륨 음소거 메소드(진짜)
  void _onPlaybackConfigChanged() {
    if (!mounted) return;


    -----------------
    //(22.3)
    // final muted = context.read<PlaybackConfigViewModel>().muted;
    final muted = ref.read(playbackConfigProvider).muted;
    -----------------
    
        -----------------
    //(22.3)
    // if (false) {
    ref.read(playbackConfigProvider.notifier).setMuted(!muted);
    if (muted) {
      _videoPlayerController.setVolume(0);
    } else {
      _videoPlayerController.setVolume(1);
    }
  }
    -----------------
    
    
// 비디오가 보여지는 영역을 계산하고 한쪽에서만 영상 재생되도록 해주는 파라메터
  void _onVisibilityChanged(VisibilityInfo info) {
    // mounted property: 위젯이 마운트되었는지 아닌지 알려줌
    if (!mounted) return;

    if (info.visibleFraction == 1 &&
        !_isPaused &&
        !_videoPlayerController.value.isPlaying) {
      //(21.4)볼륨 음소거 (진짜)
      // final autoplay = context.read<PlaybackConfigViewModel>().autoplay;


    -----------------
      //(22.3)
      // if (false) {
      if (ref.read(playbackConfigProvider).autoplay) {
        _videoPlayerController.play();
      }
    }
    -----------------
    
    
    
    //영상이 재생되고 있는 상태에서 다른 탭으로 넘어가면 리소스 폐기(dispose, 영상정지)해주는 설정
    if (_videoPlayerController.value.isPlaying && info.visibleFraction == 0) {
      _onTogglePause();
    }
    
                child: IconButton(
                  icon: FaIcon(
                  
                  
                  ---------------------
                    //(22.3)
                ref.watch(playbackConfigProvider).muted
                                  ---------------------
                                  
                                  
                    ? FontAwesomeIcons.volumeOff
                    : FontAwesomeIcons.volumeHigh,
                color: Colors.white,
              ),

              onPressed:

                                  ---------------------                                                 ---------------------   
                                    //(22.3)
                  _onPlaybackConfigChanged,
                                  ---------------------                                                    ---------------------
            ),
          ),
          Positioned(

 

22.4 AsyncNotifierProvider 

이 viewModel은 API로부터 얻어 온 값을 제공한다.

// 이 build메소드에서 우리가 원하는 API를 호출하고

// 데이터를 반환하면 그 데이터는

//우리의 Provider에 의해 expose됨(그 데이터는 VideoModel의 List 형태여야함)

//(22.4)
// class VideoTimelineScreen extends StatefulWidget {
class VideoTimelineScreen extends ConsumerStatefulWidget {
  const VideoTimelineScreen({super.key});
  @override
  //(22.4)
  VideoTimelineScreenState createState() => VideoTimelineScreenState();
}

//(22.4)
class VideoTimelineScreenState extends ConsumerState<VideoTimelineScreen> {
  int _itemCount = 4;
----------------
//스크린 넘어갈 때 느려지는 효과 없애는 함수:Pageview.builder를 컨트롤 하는 파라미터
  final PageController _pageController = PageController();
  
  
  
     const Duration(seconds: 5),
    );
  }

  @override
  Widget build(BuildContext context) {

----------------
//(22.4)
    return ref.watch(timelineProvider).when(
          loading: () => const Center(
            child: CircularProgressIndicator(),
          ),
          error: (error, StackTrace) => Center(
            child: Text(
              "Could not load videos $error",
              style: const TextStyle(
                color: Colors.white,
              ),
            ),
          ),
          data: (videos) => RefreshIndicator(
            onRefresh: _onRefresh,
            displacement: 50,
            edgeOffset: 20,
            color: Theme.of(context).primaryColor,
            child: PageView.builder(
              controller: _pageController,

              scrollDirection: Axis.vertical,

              onPageChanged: _onPageChanged,
              ----------------
              //(22.4)
              itemCount: videos.length,
----------------

              itemBuilder: (context, index) =>
                  VideoPost(onVideoFinished: _onVideoFinished, index: index),
            ),
          ),
        );
        ----------------

 

import 'dart:async';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:tiktok_clone/features/videos/models/video_model.dart';

//(22.4)
//우리의 Provider에 의해 expose됨(그 데이터는 VideoModel의 List 형태여야함)
class TimelineViewModel extends AsyncNotifier<List<VideoModel>> {
  List<VideoModel> _list = [];

  void uploadVideo() async {
    //loading state를 다시 트리거시키는 방법
    state = const AsyncValue.loading();
    await Future.delayed(
      const Duration(seconds: 2),
    );
    final newVideo = VideoModel(title: "${DateTime.now()}");
    _list = [..._list, newVideo];
    //새로운 데이더 받아오려면 이런식으로
    state = AsyncValue.data(_list);
  }

  @override
  FutureOr<List<VideoModel>> build() async {
    // 이 build메소드에서 우리가 원하는 API를 호출하고
    await Future.delayed(
      const Duration(seconds: 5),
    );

    // 데이터를 반환하면 그 데이터는
    return _list;
  }
}

final timelineProvider =
    AsyncNotifierProvider<TimelineViewModel, List<VideoModel>>(
  () => TimelineViewModel(),
);

 

//(22.4)
class VideoPreviewScreen extends ConsumerStatefulWidget {
  final XFile video;
  //동영상 저장 아이콘은 사진찍고 확인할 스크린에서만 표시
  final bool isPicked;

  const VideoPreviewScreen({
    super.key,
    required this.video,
    required this.isPicked,
  });

  @override
  //(22.4)
  VideoPreviewScreenState createState() => VideoPreviewScreenState();
}

//(22.4)
class VideoPreviewScreenState extends ConsumerState<VideoPreviewScreen> {
  late final VideoPlayerController _videoPlayerController;
  ---------------------
  
     _savedVideo = true;

    setState(() {});
  }

  ---------------------
//(22.4)
  void _onUploadPressed() {
    ref.read(timelineProvider.notifier).uploadVideo();
  }
  ---------------------
  
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        title: const Text("preview video"),
        actions: [
          //동영상 저장 아이콘은 사진찍고 확인할 스크린에서만 표시
          if (!widget.isPicked)
            IconButton(
              onPressed: _saveToGallery,
              icon: FaIcon(
                _savedVideo
                    ? FontAwesomeIcons.check
                    : FontAwesomeIcons.download,
              ),
            ),
            
              ---------------------
          //(22.4)
          IconButton(
            onPressed: ref.watch(timelineProvider).isLoading
                ? () {}
                : _onUploadPressed,
            icon: ref.watch(timelineProvider).isLoading
                ? const CircularProgressIndicator()
                : const FaIcon(
                    FontAwesomeIcons.cloudArrowUp,
                  ),
                    ---------------------
                    
                    
          ),
        ],
      ),
      body: _videoPlayerController.value.isInitialized

728x90
반응형
Comments