[Flutter] 스나이퍼팩토리 19일차

KWANWOO·2023년 2월 20일
0
post-thumbnail

스나이퍼팩토리 플러터 19일차

19일차에서는 지금까지 사용하지 않았던 유용한 위젯과 패키지들에 대해 학습했다.

학습한 내용

  • Widget
    • Stack
    • Divider
    • AnimatedOpacity
    • AnimatedContainer
    • AspectRatio
    • Wrap
  • Package
    • url_launcher
    • cached_network_image
    • intl

추가 내용 정리

유용한 위젯

  • Stack
  • Divider
    • 구분선을 넣고 싶을 때 사용

  • AnimatedOpacity
  • AnimatedContainer
    • 캐치되는 속성값을 감지하는 애니메이션

  • AspectRatio
    • 자식 위젯의 사이즈 비율을 정확히 맞춰야 할 때 사용
    • 비디오를 넣어야 할 때 (4:3)
    • 정사각형을 보여줄 때 (1:1)

  • Wrap
    • Row는 화면을 벗어나면 오버플로우가 발생하지만 Wrap은 자동으로 다음줄로 넘어가도록 함
    • (참고) [Flutter] 스나이퍼팩토리 7일차에서 정리한 내용이 있음

유용한 패키지

  • url_launcher
    • 전화걸기, 메세지 보내기, 특정 URL 웹으로 이동
    • 카카오톡 채널로 이동도 가능

  • cached_network_image
    • 이미지를 캐싱할 수 있도록 함

  • intl
    • 화폐표시, 시간표시

URI과 URL

지금까지 플러터를 계속 학습하면서 URI과 URL의 차이를 정리하고 싶었다.

  • URI
    URI는 특정 리소스를 식별하는 통합 자원 식별자(Uniform Resource Identifier)를 의미한다. 웹 기술에서 사용하는 논리적 또는 물리적 리소스를 식별하는 고유한 문자열 시퀀스이다.

  • URL
    URL은 웹 주소이며, 컴퓨터 네트워크 상에서 리소스가 어디 있는지 알려주기 위한 규약이다.

즉, URL을 URI의 서브넷으로 아래 그림과 같이 URI의 안에 URL이 포함된다.

URI의 구조는 아래와 같다.

URI의 구조

scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
  1. scheme : 사용할 프로토콜을 뜻하며 웹에서는 http 또는 https를 사용
  2. user와 password : (서버에 있는) 데이터에 접근하기 위한 사용자의 이름과 비밀번호
  3. host와 port : 접근할 대상(서버)의 호스트명과 포트번호
  4. path : 접근할 대상(서버)의 경로에 대한 상세 정보
  5. query : 접근할 대상에 전달하는 추가적인 정보 (파라미터)
  6. fragment : 메인 리소스 내에 존재하는 서브 리소스에 접근할 때 이를 식별하기 위한 정보

플러그인 (Plugins)

지난번에 시리님이 패키지를 사용하면 다른사람이 만든 UI를 사용할 수 있다고 하셨다. 추가로 플러그인 이라는게 있다고 하셨는데 플러그인은 무엇일까?

결론부터 말하자면 플러그인도 다른 사람의 코드를 사용하는 패키지 이지만, 패키지는 Dart 코드로만 이루어져 있고, 플러그인은 Dart 이외의 Java, Kotlin, Swift, Javascript 등 다른 언어의 코드를 포함하고 있다.

  • 패키지(Package) : Dart 코드로만 구성됨
  • 플러그인(Plugins : Dart 이외의 다른 언어도 포함됨

앞에서 소개한 패키지 중 하나인 url_lancher 를 통해 살펴보자

url_lancher의 기능 중 하나는 웹 브라우저를 열어 주는 것인데 이 기능은 IOS, Android, macOs 등 어떤 플랫폼에서든지 같은 기능을 보여준다.

url_lancher 패키지의 소스코드를 보면 아래와 같다.

  • UrlLauncher.java
// ...
LaunchStatus launch(
    String url,
    Bundle headersBundle,
    boolean useWebView,
    boolean enableJavaScript,
    boolean enableDomStorage) {
  if (activity == null) {
    return LaunchStatus.NO_ACTIVITY;
  }

  Intent launchIntent;
  if (useWebView) {
    launchIntent =
        WebViewActivity.createIntent(
            activity, url, enableJavaScript, enableDomStorage, headersBundle);
  } else {
    launchIntent =
        new Intent(Intent.ACTION_VIEW)
            .setData(Uri.parse(url))
            .putExtra(Browser.EXTRA_HEADERS, headersBundle);
  }

  try {
    activity.startActivity(launchIntent);
  } catch (ActivityNotFoundException e) {
    return LaunchStatus.ACTIVITY_NOT_FOUND;
  }

  return LaunchStatus.OK;
}
// ...
  • FLTURLLauncherPlugin.m
// ...
- (void)launchURL:(NSString *)urlString
             call:(FlutterMethodCall *)call
           result:(FlutterResult)result {
  NSURL *url = [NSURL URLWithString:urlString];
  UIApplication *application = [UIApplication sharedApplication];

  if (@available(iOS 10.0, *)) {
    NSNumber *universalLinksOnly = call.arguments[@"universalLinksOnly"] ?: @0;
    NSDictionary *options = @{UIApplicationOpenURLOptionUniversalLinksOnly : universalLinksOnly};
    [application openURL:url
                  options:options
        completionHandler:^(BOOL success) {
          result(@(success));
        }];
  } else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    BOOL success = [application openURL:url];
#pragma clang diagnostic pop
    result(@(success));
  }
}
// ...

위의 코드들 처럼 Android에서 브라우저를 열기 위해서는 Java 코드를 사용하고, IOS에서 사파리를 열기 위해서는 Objective-C 코드를 사용한다.

url_lancher 패키지처럼 플랫폼에 따라 다른 작업이 필요해 Dart 이외의 다른 언어의 코드가 포함되어 있는 경우에 해당 패키지를 플러그인이라고 한다.


19일차 과제

  1. Hero 위젯을 사용하여 주어진 앱 만들기

1. Hero 위젯을 사용하여 주어진 앱 만들기

Hero 위젯을 사용하여 아래의 결과물을 제작하세요.

Flutter Hero 공식문서

아래 링크는 Hero 위젯의 공식문서이다. 해당 문서에서 Hero 위젯의 사용법과 예시 등을 확인할 수 있다.
Flutter - Hero animations

네크워크 요청 API

코드 작성

  • pubspec.yaml
dependencies:
  dio: ^5.0.0
  cached_network_image: ^3.2.3

pubspec.yaml에 네트워크 통신을 위해 필요한 dio를 설치하고, 이번에 학습한 이미지 캐싱을 사용해 보기 위해 cached_network_image를 설치했다.

  • main.dart
import 'package:flutter/material.dart';
import 'package:my_app/page/home_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(), //홈 페이지 호출
    );
  }
}

main.dart에서는 HomePage 위젯을 호출한다.

  • home_page.dart
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:my_app/page/hero_page.dart';

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  Future<List> getData() async {
    var url =
        'https://sfacassignment-default-rtdb.firebaseio.com/.json'; // 데이터 요청 URL
    Dio dio = Dio(); //Dio 객체

    var res = await dio.get(url); //데이터 요청
    return res.data['body']; //결과 반환
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('19일차 과제'),
      ),
      body: FutureBuilder(
        future: getData(), //데이터 요청 바인딩
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            //데이터를 받아온 뒤 리스트 뷰 출력
            return ListView.builder(
              padding: const EdgeInsets.all(16),
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) => Padding(
                padding: const EdgeInsets.all(8.0),
                child: Row(
                  children: [
                    GestureDetector(
                      child: Hero(
                        tag: index, //히어로 태그
                        child: CachedNetworkImage(
                          width: 64,
                          height: 64,
                          fit: BoxFit.fill,
                          imageUrl: snapshot.data![index]['url'],
                        ),
                      ),
                      //이미지 상세 페이지로 이동
                      onTap: () => Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) => HeroPage(
                            imgUrl: snapshot.data![index]['url'],
                            heroTag: index,
                          ),
                        ),
                      ),
                    ),
                    SizedBox(width: 16),
                    Text(snapshot.data![index]['msg']), //이미지관련 메세지
                  ],
                ),
              ),
            );
          } else {
            return Center(child: CircularProgressIndicator()); // 로딩 중
          }
        },
      ),
    );
  }
}

HomePage에서는 우선 데이터를 받아오는 getData()메소드를 작성했다. 데이터는 dioget 메소드를 사용해 가져오고 결과를 리턴한다.

본문에서는 FutureBuilder를 통해 결과 값이 정상적으로 받아오면 리스트 뷰를 출력한다. 리스트 뷰에서는 Row로 요소들을 구성했다. 이미지는 CachedNetworkImage를 사용해 보았다. 이렇게 캐시를 사용하면 한 번 가져온 이미지를 캐시에 저장하고 사용하기 때문에 데이터가 많아졌을 때 서버에 요청을 하지 않아도 돼서 성능이 좋아질 수 있다.

이렇게 출력한 이미지를 Hero로 감싸고 태그를 index로 설정하여 리스트의 각 항목들이 다른 고유한 태그 값을 가지도록 했다.

이러한 이미지 전체를 GestureDetector를 사용해 onTap 이벤트를 구현해 주었다. 이미지를 클릭시 이미지가 확대되는 상세 페이지로 이동하도록 HeroPage를 작성했는데 이 코드는 뒤에서 설명한다.

마지막으로 이미지의 메세지는 Text로 출력했다.

  • hero_page.dart
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';

class HeroPage extends StatelessWidget {
  const HeroPage({super.key, required this.heroTag, required this.imgUrl});

  final int heroTag; //히어로 태그
  final String imgUrl; //이미지 URL

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Second Page'),
      ),
      body: Center(
        child: Hero(
          tag: heroTag, //받아온 히어로 태그
          // 이미지 출력
          child: CachedNetworkImage(
            imageUrl: imgUrl,
          ),
        ),
      ),
    );
  }
}

HeroPage는 히어로 애니메이션을 위해 사용할 태그와 이미지 Url을 매개변수로 받는다. 이를 사용해 화면에 이미지를 출력하는데 역시 CachedNetworkImage를 사용해 보았고, 전달 받은 히어로 태그를 설정했다. 이렇게 Hero 위젯에 같은 태그를 설정하면 페이지를 이동할 때 애니메이션이 적용된다.

결과


오랜만에 짧은 포스팅 ㅎㅎ

정말 오랜만에 포스팅이 짧게 끝난 것 같다. 오늘 학습한 내용이 간단한 위젯들과 패키지이고, 과제도 어렵지 않고 양도 적어서 금방 끝났다. 추가 내용 정리를 어떤걸 할까 고민하다가. 예전부터 생각했던 URL과 URI 차이를 정리해 보았고, 지난번에 시리님이 플러그인도 찾아보면 좋을거 같다고 하셨는데 갑자기 생각나서 오늘 포스팅에 사용했다. ㅎㅎ 그런 이유로 오늘은 추가 내용 정리가 학습한 내용과 크게 연관은 없다. ㅋㅋㅋㅋ 근데 쓰다가 어딘지 모르겠는데 마크다운이 혼자 고장나서 약간 애를 먹었다. ㅠㅠ 그럼 이만 이틀 뒤에 월간평가가 있어서 복습을 열심히 하러 가보겠습니다!!

(사실 월간평가가 있어서 오늘 포스팅 내용과 과제가 많지 않았던 것입니다..ㅠㅠ 아마 내일도 내용은 적을 듯...?)

📄 Reference

profile
관우로그

0개의 댓글

관련 채용 정보