Firebase웹에서 컬렉션과 샘플 데이터까지 만들었다고 가정할 시 

맥에서 flutter를 사용하여 Firebase와 연동하려고 하는 방법을 설명한다 

 

제일 처음 터미널을 켜서 아래와 같이 입력한다 

curl -sL https://firebase.tools | bash

 

설치 도중 기기의 암호를 입력하는 단계가 있으므로 잊지말고 암호를 입력하자

그러면 설치가 완료 되는데, 

 

완료 이후  아래와 같은 명령어로 firebase에 로그인을 해준다

firebase login

 

명령어를 실행하면 웹에서 연동된 구글 계정으로 로그인 할 수 있다. 

로그인한 이후 

 

다시 터미널로 돌아가서 flutter와 firebase간 연동을 쉽게해주는 dart 패키지를 받을건데  명령어는 다음과 같다

dart pub global activate flutterfire_cli

 

설치가 완료되면 다음 사진과 같이 화면이 뜰텐데

경고의 내용은 심플하게 경로에있는 내용을 현재 쉘rc 파일에 환경설정으로 추가하란 말이다.  

 

경고를 따라서 다음과같이 명령어를 입력하고 내용을 복사하여 ~/.zshrc에 환경변수를 선언했다

vim ~/.zshrc

 

환경변수를 선언하고 vscode terminal에서 다음과 같이 명령어를 치면 사진과 같이 있는 프로젝트를 선택할 수 있는데 

거기서 맞는 프로젝트를 선택하면 된다. 

flutterfire configure

 

프로젝트를 선택했으면 다음으로 어떤 플랫폼에 연동할건지 선택을 할 수 있는데

이번 프로젝트는 앱으로만 만들예정이므로 android 및 ios만 선택하고 나머지는 space키로 선택해제 한다

 

다음으로는 android가 선택이 되어있으므로 패키지 명을 입력해줘야 하는데

입력은 com.example.flutter_Firebase_blog_app으로 해당 이름은 root/android/app/build.gradle에서 찾을 수 있다. 

 

아래 사진에서처럼 namespace를 확인해주면 된다. 

 

하지만 진행하다가 다음과 같은 에러를 마주했는데 찾아보니 ruby version이 너무 낮아서 생기는 문제였다 

(https://totally-developer.tistory.com/176 를 참조하여 ruby 버전 올리고 진행함)

 

 

해결하면 다음과 같이 완료된 모습을 볼 수 있다

 

 

아래명령어를 통해 flutter 패키지에 firebase core 플러그인과 firestore관련 플러그인을 추가해준다 

flutter pub add firebase_core
flutter pub add cloud_firestore

 

에러가 많았다..

 

하지만 결국 해냈고 

 

앱이 실행되는게 확인 되면(빌드하는데 4분걸렸다..) 

main함수에 다음과같이 runApp 위에 다음과 같이 추가해준다 

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  runApp(const ProviderScope(child: MyApp()));
}
728x90

자주 사용하는 ListView 에서 아이템간 공간을 확보하고 싶을때 쓸수 있는 요긴한 방법이다. 

 

사용법은 아래와 같은데, 

일단 ItemCount를 받아야 하고

어떤 아이템을 Building할건지 정한다음 

seperatorBuilder에서 각 아이템간 삽입할 아이템을 정의하면 된다. 

 

아래 예제 같은경우에 각 요소마다 SizedBox(height:10)을 삽입하고 있는걸 확인할 수 있다. 

  Expanded(
    child: ListView.separated(
      itemCount: 10,
      itemBuilder: (context, index) => item(),
      separatorBuilder: (context, index) => SizedBox(
        height: 10,
      ),
    ),
  ),

 

728x90

플러터에서 Navigator를 사용하려면 context를 반드시 받아야 하는데,

만약 Widget을 method로 빼서 사용한다면 context를 인자로 넘겨줘야 할것이다. 

하지만 그렇게 넘겨주게 되면 번거로운작업이 되고 햇갈릴 가능성이 농후하기 때문에 그때는 Builder Widget을 사용하면 된다. 

 

사용방법은 기존에 있는 Widget최상단에서 Builder WIdget으로 감싼뒤 

안에 builder속성에 context를 넣고 기존에 있는 Widget을 리턴하면 

context를 사용할 수 있다. 

 

예제는 아래와 같다. 

 Widget item() {
    return Builder(builder: (context) {
      return GestureDetector(
        onTap: () {
          Navigator.push(
              context, MaterialPageRoute(builder: (context) => DetailPage()));
        },
        child: Container(
          width: double.infinity,
          height: 120,
          child: Stack(
            children: [
              Positioned(
                width: 120,
                height: 120,
                right: 0,
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(20),
                  child: Image.network(
                    'https://picsum.photos/200/300',
                    fit: BoxFit.cover,
                  ),
                ),
              ),
              Container(
                width: double.infinity,
                height: double.infinity,
                margin: EdgeInsets.only(right: 100),
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(20),
                ),
                padding: EdgeInsets.all(20),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'data',
                      style:
                          TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
                    ),
                    Spacer(),
                    Text(
                      'dighdighdighdighdighdighdighdighdighdighdighdighdighdighdigh',
                      overflow: TextOverflow.ellipsis,
                      style: TextStyle(color: Colors.grey, fontSize: 12),
                    ),
                    SizedBox(
                      height: 4,
                    ),
                    Text(
                      '2024/12/03 : 13:01:05',
                      style: TextStyle(color: Colors.grey, fontSize: 12),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      );
    });
  }

 

728x90

플러터에서 웹뷰는 iOS, Android의 네이티브 컴포넌트를  사용하여 띄움

따라서 패키지를 추가시켜줘야함

크게 2가지 webView가 있는데

  -> flutter_webview: flutter공식팀에서 만들고 관리하지만 기능이 많이 없음

  -> flutter_inappwebview: 3rd party지만 여러 기능이 있음 

기능이 많이 없는관계로 보통 inappwebview를 많이 쓴다고 한다 

 

추가하는것은 간단하게 flutter pub add flutter_inappwebview로 패키지를 추가해주고 

다음과 같이 추가하여 쓸수 있다. 

InAppWebView(
    // InAppWebView 최초 요청할 URL
    initialUrlRequest: URLRequest(
      url: WebUri("https://www.naver.com/"),
    ),
    // WebView 설정 : 외우지 마시오! 버전바뀌면 사용법 바뀌니까 이런설정을 할 수 있다 정도로만 학습바랍니다!
    initialSettings: InAppWebViewSettings(
      // 사용자 제스쳐 없이도 비디오, 오디오 자동재생 가능여부
      mediaPlaybackRequiresUserGesture: true,
      // 페이지 javascript 활성화여부. 웹 브라우저에서는 js 필수불가결한 요소라 true!
      javaScriptEnabled: true,
      // 요청하는 클라이언트의 브라우저 종류, 운영체제, 장치 정보 등을 서버에서 알 수 있게 보낼때 같이 보냄
      // 디폴트로 웹뷰로 되어 있는데 일부 웹페이지에서는 웹뷰로 접속시 차단하는 페이지도 있으니 꼭 설정!
      userAgent:
          'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
    ),
    // WebView 네이티브 컴포넌트가 만들어지면 호출됨
    onWebViewCreated: (controller) {
      print('onWebViewCreated');
    },
    // 페이지 로딩이 시작될 때 호출됨
    onLoadStart: (controller, url) {
      print('onLoadStart');
    },
    // 페이지 로딩이 완료되면 호출됨
    onLoadStop: (controller, url) {
      print('onLoadStop');
    },
    // 웹뷰 내 웹 페이지에서 GPS, 카메라 등의 권한을 요청했을때 호출됨
    onPermissionRequest: (controller, request) async {
      print('onPermissionRequest');
      return null;
    },
  )
728x90

flutter create를 통해 프로젝트를 생성하면 아래 사진과 같은 구조로 파일구조가 생성되게 된다. 

파일구조를 살펴보자 

 

제일먼저 lib 폴더는 프로그램에서 사용되는 dart파일을 넣어두는 폴더이다.(사진에서도 main.dart가 포함되어있다)
실제 여기서 여러 폴더구조를 만들고 파일을 생성하여 코딩을 하는 폴더이다 

 

다음 android, ios, linux, macos, web폴더인데 각각 디바이스에서 사용할 수 있는 센서등을 사용할때
native언어로 코드를 작성해서 사용하면 된다고 한다 

 

test폴더는 TDD를 위해 lib폴더의 테스트를 생성하기 위한 폴더이다 

 

다음으로 pubspec.yaml은 프로젝트 구성에 대해 선언이 있는곳인데, SDK버전이나, 사용할 라이브러리등을 선언한다
사용방법은 pub.dev에서 선언되있는 dependency를 긁어서 복붙하거나 flutter pub add명령어를 사용해서 라이브러리를 추가하는 방식이다 

 

그 외 파일들은 딱히 프로젝트 사용에 있어 건드릴 필요가 없어 소개는 여기까지 하기로 한다. 

728x90

플러터에는 기본적으로 url을 연결할 수 있는 하이퍼링크 기능이 없다...

 

한참 삽질끝에 방법을 알아내었다 

 

일단 다음과 같이 url_launcher플러그인을 설치한다. 

flutter pub add url_launcher

 

그 다음 https로 연결을 위해 ios에서 권한을 부여해 줘야 한다

권한은 root -> ios -> Runner -> Info.plist에서 

<dict> </dict> 사이에 다음과 같이 추가한다

sms, 전화는 각각 string 안에 sms, tel값을 추가하면 된다 .

<key>LSApplicationQueriesSchemes</key>
    <array>
      <string>https</string>
    </array>

 

 

다음에 버튼이나 이벤트리스너에서 다음과 같이 Uri를 생성하고 url을 launch 해주면 된다 

잊지말고 async를 붙여주도록하자 

final url=Uri.parse('https://google.com');
await launchUrl(url);

 

728x90

상태관리로써 StatefulWidget을 사용할 수도 있지만 한계가 존재한다 

 

상태관리가 복잡해 진다거나, 단순화 하려고 하면 성능에 이슈가 생기거나 하는 식이다

 

여러 이슈들을 해결하기 위해 GetX라는 3rd party Library가 만들어 졌다.

기능도 아주 많고 빠르다고 하니 앞으로는 상태관리로 이 친구를 많이 쓸거같다 

 

기본적으로 설치는 프로젝트에서 다음과 같이 진행된다 

flutter pub add get

 

 

이후 상태관리를 하려고 하는 클래스에서 다음과 같이 extends로 상속한다

import 'package:get/get_state_manager/get_state_manager.dart';
import 'package:plmanchu/info/memberInfo.dart';

class Membercontroller extends GetxController {
  List<Memberinfo> members = [
    Memberinfo(
        name: "***",
        MBTI: "****",
        image: 'lib/assets/images/2.png',
        hobbies: ["피아노", "드럼"],
        major: "컴공",
        careers: ["클라우드엔지니어", "k8s개발자"],
        targetApp: "당근마켓",
        introduce: "잘부탁드립니다",
        radarChart: 'lib/assets/images/radarChart.png'),
  ];
}

주의해야 할 점은 member class가 따로 존재해야 한다는 것이다. 

 

이후 main.dart파일에서 의존성을 주입하기 위해 다음과 같이 GetX를 적용한다

* 주의할점 GetX를 사용하면 MaterialApp에서 GetMaterialApp으로 타입을 변경해야 한다

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:plmanchu/info/memberInfo.dart';
import 'package:plmanchu/pages/intro.dart';

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    Get.put(Membercontroller())
//  return MaterialApp(
    return GetMaterialApp(

      title: 'PlmanchuIntroducing',
      // theme 은 설정 안해주나요?
      home: IntroPage(),
    );
  }
}

 

 

이후 GetX의 컨트롤러를 사용해서 이벤트를 작동시키기 위해 아래와 같이 

Get.find<controller이름>().(method명)(); 으로 작동시킨다  

아래 코드에서 builder안에, 그리고 Image안에 각각 맴버별로 이미지와 정보를 생성함을 알수 있다. 

children: [
      ...List<Widget>.generate(
          Get.find<Membercontroller>().getMemberLength(), (index) {
        return GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                  builder: (context) => specA(
                        memberInfo: Get.find<Membercontroller>()
                            .getMember(index),
                      )),
            );
          },
          child: Image.asset(
              Get.find<Membercontroller>().getMember(index).image),
        );
      }),
    ],

 

내부에서 동적으로 데이터가 변경되는 경우 다음과 같이 update() 함수를 사용하여 setState() 와 같이 상태를 업데이트 시켜 준다 

update();

 

이렇게 update를 트리거 시켜주면 GetBuilder로 구독한 곳에서 업데이트가 일어나게 된다 

위 소스코드상에는 GetBuilder로 업데이트되는 부분이 현재 없기 때문에 Update시킬 필요가 없다 ㅎ

구독랑이를 하려면 아래와같이 GetBuilder와 controller를 연결시켜주면 쓸수 있다

* 주의할 점은 init과 builder는 반드시 들어가야 하고 builder의 argument는 임의의 컨트롤러 이름이다

child: GetBuilder{
	init: Get.find<컨트롤러 이름>,
	builder: (controller) => {
		Widget...
	}
}
728x90

플러터에서 레이아웃을 설정하는 위젯에 대해 알아보자

 

크게 Container, SizedBox, Row, Column, Expanded, Stack&Positioned로 

총 6개정도로 나누어진다고 한다

 

역시 하나하나씩 알아보도록 하자(예제사진을 첨부하지 않겠음)

 

제일먼저 Container

코드를 살펴보면

Container(
  padding: const EdgeInsets.only(
    left: 20,
    right: 20,
  ),
  decoration: BoxDecoration(
    gradient: LinearGradient(
      colors: [
        Color.fromARGB(255, 255, 59, 98).withOpacity(0.7),
        Color.fromARGB(255, 255, 59, 98)
      ],
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
    ),
    borderRadius: BorderRadius.circular(10),
    boxShadow: [
      BoxShadow(
        color: Color.fromARGB(255, 255, 59, 98).withOpacity(0.5),
        spreadRadius: 5,
        blurRadius: 7,
        offset: Offset(0, 3), // changes position of shadow
      ),
    ],
  ),
  width: 200,
  height: 150,
  child: Center(
      child: Text(
    'Container',
    style: TextStyle(color: Colors.white),
  )),
),

 

이코드 같은 경우 width 200 height 150의 컨테이너가 생성되고 

안에 그라디언트(decoration옵션)로 투명을 준 색깔이 컬러풀하게 빛나는 걸 확인 할 수 있다. 


다음으로 SizedBox

코드로 살펴보자

 

Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Container(
        color: Colors.red,
        width: 100,
        height: 40,
      ),
      const SizedBox(height: 10),
      Container(
        color: Colors.blue,
        width: 100,
        height: 40,
      ),
    ],
),

 

특별한게 없다 각 요소 혹은 widget사이에 padding처럼 역할을 수행할 수 있는 SizedBox이다 

height옵션은 높이 즉,위아래 간격으로 나눌때 width옵션은 너비, 즉 좌우로 나눌때 쓰면 된다. 


다음으로 Row 와 Column

코드로 살펴보자 

 

Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: List.generate(
    5,
    (index) => Container(
      width: 40,
      height: 40,
      color: Colors.red,
      margin: const EdgeInsets.all(5),
    ),
  ),
),

 

Row로 선언 이후에 LIst.generate로 5개 요소를 균등하게 만들어 주고 있다

Row는 말그대로 가로방향으로 요소를 나열하는 LayoutWidget이다 

Column은 Row와 반대로 세로방향으로 요소를 나열하는 LayoutWidget이다 라고 알아두면 된다


다음으로 Expanded

역시 코드로 살펴보자 

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    Expanded(
      child: Container(
        height: 40,
        color: Colors.red,
        margin: const EdgeInsets.all(5),
      ),
    ),
    ...List.generate(
      4,
      (index) => Container(
        width: 40,
        height: 40,
        color: Colors.red,
        margin: const EdgeInsets.all(5),
      ),
    ),
  ],
),

 

Row & Column을 사용할때 많이 합쳐서 사용한다고 한다 

Expanded뜻은 확장하다라는 뜻인데 그냥 Row로 선언한 요소의 경우 딱 그 필요한 width만 차지하는 반면

Expanded는 가로영역 전체를 다 차지하게 된다. 

 

CSS에서 존재하는 Flex옵션도 있는데 비율로 자리를 차지하는 옵션이다.

코드는 다음과 같다 

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    Expanded(
      flex: 1, // 1
      child: Container(
        height: 40,
        color: Colors.red,
        margin: const EdgeInsets.all(5),
      ),
    ),
    Expanded(
      flex: 3, // 2
      child: Container(
        height: 40,
        color: Colors.red,
        margin: const EdgeInsets.all(5),
      ),
    ),
    Expanded(
      flex: 2, // 1
      child: Container(
        height: 40,
        color: Colors.red,
        margin: const EdgeInsets.all(5),
      ),
    ),
  ],
),

마지막으로 Stack&Positioned

사진으로 간단하게 살펴보면 아래 사진과 같이 프로필 밑에 사진버튼같은걸 더할때 Stack을 사용하게 된다. 

코드로 살펴보자 

Stack(
  children: [
    const CircleAvatar(
      radius: 50,
      child: Icon(
        Icons.person,
        size: 40,
      ),
    ),
    Positioned(
      bottom: 0,
      right: 0,
      child: Container(
        padding: const EdgeInsets.all(7),
        decoration: const BoxDecoration(
            shape: BoxShape.circle, color: Colors.white),
        child: const Icon(
          Icons.camera_enhance,
          size: 24,
        ),
      ),
    )
  ],
),

 

Stack을 선언하고 아래 children으로 CircleAvatar과 그 위에 띄워지는 Positioned로 선언된 카메라 아이콘이 있다 

Bottom, Right 옵션을 주면 우하단에 Stack이 위치하게 되며 

Top, Left 옵션을 주게되면 좌상단에 Stack이 위치한다

 

 

 

이렇게 LayoutWidget의 종류를 알아보았다. 

 

PageView와 함께 사용하면 여러가지 레이아웃을 만들수 있을거같다. 

 

 

728x90

+ Recent posts