반응형

이번 글에서는 주사위 앱 만들기에 대해서 알아봅니다.

 

주사위 앱 레이아웃

 - BottomNavigationBar 위젯을 이용해서 화면 전환을 한다.

 - RootScreen 위젯 : 상단과 하단으로 나누어져 있는 위젯입니다. 하단에는 화면 전환을 할 수 있는 BottomNavigationBar 위젯을 위치 시키고, 상단에는 TabBarView 를 통해 선택된 화면을 보여줍니다. 화면 전환은 BottomNavigationBar 의 탭을 클릭하거나 TabBarView 에서 좌우 스크롤을 통해 이루어집니다. TabBarView 에 나타내는 화면은 HomeScreen 위젯과 SettingsScreen 위젯 두 화면입니다.

 - HomeScreen 위젯 : 주사위 이미지를 위치시킬 Image 위젯, 글자를 작성할 Text 위젯, 주사위 숫자를 나타낼 Text 위젯으로 이루어져 있습니다.

 - SettingsScreen : 민감도를 정의하는 Slider 위젯을 위치시키고 이 Slider 를 좌우로 이동시켜 민감도를 정합니다. Text 위젯을 이용하여 레이블을 작성합니다.

 

주사위 앱 shake 플러그인 및 이미지 추가

 - pubspec.yaml

....

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  shake: 2.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  assets:
    - asset/img/
  
  ....

 

주사위 앱에서 사용할 상수 정의

 - 글자 크기, 자주 사용하는 색상등을 반복적으로 사용하는 값들을 미리 정의해놓고 사용하기 위해서 정의한다.

 - 일괄적으로 변경 시 유용하게 사용됩니다.

 - [프로젝트 디렉토리]/lib/const/colors.dart

import 'package:flutter/material.dart';

const backgroundColor = Color(0xFF0E0E0E);

const primaryColor = Colors.white;

final secondaryColor = Colors.grey[600];

※ secondaryColor : grey[600] 은 런타임시에 색상이 계산되기 때문에 const 사용이 불가하다. 그래서 final 로 정의합니다.

 

 

주사위 앱 위젯 구현

1. HomeScreen

 - [프로젝트 디렉토리]/lib/screen/home_screen.dart

import 'package:[프로젝트 명]/const/colors.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  final int number;

  const HomeScreen({
    required this.number,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [

        // ➊ 주사위 이미지
        Center(
          child: Image.asset('asset/img/$number.png'),
        ),
        SizedBox(height: 32.0),
        Text(
          '주사위 숫자',
          style: TextStyle(
            color: secondaryColor,
            fontSize: 20.0,
            fontWeight: FontWeight.w700,
          ),
        ),
        SizedBox(height: 12.0),
        Text(
          number.toString(),  // ➋ 주사위 값에 해당되는 숫자
          style: TextStyle(
            color: primaryColor,
            fontSize: 60.0,
            fontWeight: FontWeight.w200,
          ),
        ),
      ],
    );
  }
}

 - Column 위젯을 사용하여 Image 위젯과 Text 위젯을 배치합니다. 화면에 보여질 숫자를 RootScreen 위젯에서 정의하도록 생성자를 통해서 number 매개변수로 입력받는다.

 - 생성자로 입력받은 number 매개변수 값을 이용하여 Image 위젯에는 해당하는 숫자 이미지를, Text 위젯에는 숫자 값을 표시합니다.

 

2. SettingsScreen

 - [프로젝트 디렉토리]/lib/screen/settings_screen.dart

import 'package:[프로젝트 명]/const/colors.dart';
import 'package:flutter/material.dart';

class SettingsScreen extends StatelessWidget {
  final double threshold;  // Slider의 현잿값

  // Slider가 변경될 때마다 실행되는 함수
  final ValueChanged<double> onThresholdChange;

  const SettingsScreen({
    Key? key,

    // threshold와 onThresholdChange는 SettingsScreen에서 입력
    required this.threshold,
    required this.onThresholdChange,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Padding(
          padding: const EdgeInsets.only(left: 20.0),
          child: Row(
            children: [
              Text(
                '민감도',
                style: TextStyle(
                  color: secondaryColor,
                  fontSize: 20.0,
                  fontWeight: FontWeight.w700,
                ),
              ),
            ],
          ),
        ),
        Slider(
          min: 0.1,  // ➊ 최소값
          max: 10.0,  // ➋ 최대값
          divisions: 101,  // ➌ 최소값과 최대값 사이 구간 개수
          value: threshold,  // ➍ 슬라이더 선택값
          onChanged: onThresholdChange,  // ➎ 값 변경 시 실행되는 함수
          label: threshold.toStringAsFixed(1),  // ➏ 표시값
        ),
      ],
    );
  }
}

 - SettinsScreen 위젯은 Text 위젯과 Slider 위젯으로 이루어져 있습니다. Column 위젯을 이용하여 Text 위젯과 Slider 위젯을 세로로 배치합니다.

 - Slider 위젯 : 좌우로 움직일 때 onChanged 매개변수에 정의된 콜백함수가 호출됩니다. onChanged 매개변수로 입력받은 현재값을 State에 저장하고 다시 value 매개변수에 같은 값을 입력하게 됩니다.

 - Slider 위젯은 현재값과 onChanged 매개변수는 RootScreen 에서 입력받습니다.

 

3. RootScreen

 - [프로젝트 디렉토리]/lib/screen/root_screen.dart

import 'package:flutter/material.dart';
import 'package:[프로젝트 명]/screen/home_screen.dart';
import 'package:[프로젝트 명]/screen/settings_screen.dart';
import 'dart:math';
import 'package:shake/shake.dart';

class RootScreen extends StatefulWidget {
  const RootScreen({Key? key}) : super(key: key);

  @override
  State<RootScreen> createState() => _RootScreenState();
}

class _RootScreenState extends State<RootScreen> with TickerProviderStateMixin{    // ➊
  TabController? controller;  // 사용할 TabController 선언
  double threshold = 2.7;
  int number = 1;
  ShakeDetector? shakeDetector;

  @override
  void initState() {
    super.initState();

    controller = TabController(length: 2, vsync: this);  // ➋

    controller!.addListener(tabListener);
    shakeDetector = ShakeDetector.autoStart(   // ➊ 흔들기 감지 즉시 시작
      shakeSlopTimeMS: 100,  // ➋ 감지 주기
      shakeThresholdGravity: threshold,  // ➌ 감지 민감도
      onPhoneShake: onPhoneShake,  // ➍ 감지 후 실행할 함수
    );
  }

  void onPhoneShake() {  // ➎ 감지 후 실행할 함수
    final rand = new Random();

    setState(() {
      number = rand.nextInt(5) + 1;
    });
  }

  tabListener() {  // ➋ listener로 사용할 함수
    setState(() {});
  }

  @override
  dispose(){
    controller!.removeListener(tabListener); // ➌ listener에 등록한 함수 등록 취소
    shakeDetector!.stopListening();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TabBarView(  // ➊ 탭 화면을 보여줄 위젯
        controller: controller,
        children: renderChildren(),
      ),

      // ➋ 아래 탭 네비게이션을 구현하는 매개변수
      bottomNavigationBar: renderBottomNavigation(),
    );
  }

  List<Widget> renderChildren(){
    return [
      HomeScreen(number: number),
      SettingsScreen(  // 기존에 있던 Container 코드를 통째로 교체
        threshold: threshold,
        onThresholdChange: onThresholdChange,
      ),
    ];
  }

  void onThresholdChange(double val){  // ➊ 슬라이더값 변경 시 실행 함수
    setState(() {
      threshold = val;
    });
  }

  BottomNavigationBar renderBottomNavigation() {
    return BottomNavigationBar(
      currentIndex: controller!.index,
      onTap: (int index) {  // ➎ 탭이 선택될 때마다 실행되는 함수
        setState(() {
          controller!.animateTo(index);
        });
      },
      items: [
        BottomNavigationBarItem(  // ➊ 하단 탭바의 각 버튼을 구현
          icon: Icon(
            Icons.edgesensor_high_outlined,
          ),
          label: '주사위',
        ),
        BottomNavigationBarItem(
          icon: Icon(
            Icons.settings,
          ),
          label: '설정',
        ),
      ],
    );
  }
}

 - TabBarView 는 TabController 가 필수입니다. TabController 는 위젯이 생성될 때 딱 한번만 초기화되어야 하니 initState() 에서 초기화 작업을 합니다. TabController vsync 기능을 사용하려면 필수로 TickerProviderStateMixin을 사용해야 합니다. TickerProviderStateMixin 은 애니메이션의 효율을 올려주는 역할을 합니다. TabController 의 length 매개변수에는 탭의 개수를 나타내고, TickerProviderStateMixin 를 사용하는 State 클래스를 this 로 넣어줍니다. 그러면 controller 를 이용해서 TabBarView 를 조작할 수 있습니다. controller 의 속성이 변할 때마다 실행시킬 콜백함수를 addListener() 함수를 통해 등록할 수 있습니다. setState() 를 실행하여 controller 의 속성이 변경될 때 build() 를 재실행하도록 합니다.

 - ShakeDetector : Shake 플러그인은 흔들기를 인지할 때마다 실행할 함수를 등록합니다. 얼마나 자주 흔들기 감지 주기, 감지 민감도, 감지 시 실행할 콜백함수 등을 매개변수로 전달합니다. 흔들 때마다 1 ~ 6 사이의 난수를 생성합니다.

 - renderChildren() : TabBarView 위젯을 반환하는 함수입니다. TabBarView 위젯을 이용하면 각종 Tab 위젯과 쉽게 연동 할 수 있는 UI 를 구현할 수 있습니다. 기본 애니메이션이 제공되며 children 매개변수에 각 탭의 화면에서 활용하고 싶은 위젯을 List 로 넣어줍니다. bottomNavigation 매개변수에 BottomNavigationBar 를 넣어주면 쉽게 Tab을 조정할 수 있는 UI 를 하단에 배치할 수 있습니다. 주사위 앱에서는 TabBarView 의 첫번째 화면에 HomeScreen, 두번째 화면에 SettinsScreen 을 표시합니다.

 - renderBottomNavigation() : BottomNavigationBar 위젯을 반환하는 함수입니다. BottomNavigationBar 에 제공될 각 탭은 BottomNavigationBar 위젯의 items 매개변수에 제공해주면 됩니다. BottomNavigationBarItem 의 icon 매개변수와 label 매개변수를 이용해서 구현합니다. 탭을 클릭했을 때 TabBarView 와 화면을 동기화하기 위해 animateTo() 를 실행하여 자연스러운 애니메이션으로 TabBarView 를 전환시킵니다.

 

4. 주사위 앱 실행 첫 화면 : main

 - [프로젝트 디렉토리]/lib/main.dart

import 'package:flutter/material.dart';
import 'package:[프로젝트 명]/screen/home_screen.dart';
import 'package:[프로젝트 명]/const/colors.dart';
import 'package:[프로젝트 명]/screen/root_screen.dart';

void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        scaffoldBackgroundColor: backgroundColor,
        sliderTheme: SliderThemeData(  // Slider 위젯 관련
          thumbColor: primaryColor,    // 동그라미 색
          activeTrackColor: primaryColor,  // 이동한 트랙 색

          // 아직 이동하지 않은 트랙 색
          inactiveTrackColor: primaryColor.withOpacity(0.3),         ),
        // BottomNavigationBar 위젯 관련
        bottomNavigationBarTheme: BottomNavigationBarThemeData(
          selectedItemColor: primaryColor,     // 선택 상태 색
          unselectedItemColor: secondaryColor, // 비선택 상태 색
          backgroundColor: backgroundColor,    // 배경 색
        ),
      ),
      home: RootScreen(),
    ),
  );
}

 - Theme 설정 : 상수를 사용해서 테마를 적용합니다.

 - 홈 화면 : RootScreen 위젯으로 설정합니다.

 

 

주사위 앱을 실제 기기에서 실행해봅니다.

 - 주사위가 보이면 핸드폰을 흔들어서 난수가 생성되고 새로운 숫자와 주사위가 보이는 것을 확인합니다.

 - TabBarView 에서 좌우 스크롤하거나 BottomNavigationBar 를 클릭하여 화면 전환이 되는지 확인합니다.

 - [설정] 탭을 클릭하여 설정화면으로 이동한 뒤 Slider 위젯을 이용하여 민감도를 올립니다.

 - [주사위] 탭을 클릭하여 HomeScreen 화면으로 이동한 뒤 핸드폰을 더 강하게 흔들어야 난수가 발생하는지 확인합니다.

 

이상 플러터에서 주사위 앱을 만들고 실행까지 해보았습니다.

 

반응형
반응형

이전 글에서 콜백 함수, 웹뷰 위젯에 대해서 알아보았다.

이를 이용하여 이제 웹 앱 만들기에 도전해보자.

이미 만들어진 웹 페이지를 앱에서 불러와서 보여주는 앱을 웹 앱이라 부른다.

웹 앱을 만들기 전에 사전 설정이 필요하다.

 

1. 플러그인 추가

 - pubspec.yaml

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2

  # 웹뷰 플러그인 추가.
  webview_flutter: 3.0.4

위와 같이 웹뷰 플러그인을 추가하고 "pub get" 을 클릭하여 플러그인을 내려받는다.

"pub get" 버튼을 클릭하지 않고 플러그인을 내려받는 방법은...

아래처럼 안드로이드 스튜디오 하단의 "터미널" 탭에서 명령어를 실행하는 것이다.

- flutter pub get

 

2. 권한 및 네이티브 설정

 - 웹 앱을 만들기 위해 인터넷 사용 권한을 추가하고 http / https 프로토콜을 이용할 수 있게 설정해야 한다.

 - android/app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.flutter_sample">
....
   <uses-permission android:name="android.permission.INTERNET" />
....
</manifest>

 

 - 안드로이드 빌드 툴인 gradle 설정 파일인 build.gradle 은 모듈 파일로써 의존성이나 버전 정보를 관리한다.

 - android/app/build.gradle(android/build.gradle 파일은 프로젝트 파일이며 주로 클래스패스나 repository 정보등을 설정한다)

....

android {
    // 변경.
    // compileSdkVersion flutter.compileSdkVersion
    compileSdkVersion 32

....

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.example.flutter_sample"
        // You can update the following values to match your application needs.
        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
        // 변경
        // minSdkVersion flutter.minSdkVersion
        minSdkVersion 20
        targetSdkVersion flutter.targetSdkVersion
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }

....
}

....

 - minSdkVersion : 안드로이드 운영체제의 최소 SDK 버전을 설정

 - compileSdkVersion : 앱을 빌드할 때 사용할 SDK 버전. 앱은 compileSdkVersion 이하 버전의 기능을 모두 지원한다.

이런 네이티브 설정 관련 정보는 각 플러그인의 pub.dev 페이지에서 확인 할 수 있다.참고로 webview_flutter 플러그인 정보는 webview_flutter | Flutter Package (pub.dev) 에서 확인 할 수 있다.

아직 많은 사이트들이 https 가 아닌 http 만 지원하는 곳도 있을 수 있다. 따라서 http 프로토콜을 허용하도록 설정한다.

 - android/app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.flutter_sample">

   <uses-permission android:name="android.permission.INTERNET" />

   <application
        android:label="flutter_sample"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher"
        android:usesCleartextTraffic="true"> <!-- 여기 부분 추가 -->
        
       ....
       
</manifest>

 

다음 글에서 본격적으로 웹 앱을 만들어 보도록 하자!!

반응형
반응형

이제 드디어 코딩해볼 시간!!

가장 기본인 Hello World 를 출력해보자!!

 

플러터 프로젝트를 생성하면 [프로젝트 폴더]/llib/main.dart 파일이 생긴다.

파일의 내용을 전부 지운 후 아래 코드를 입력.

// 머터리얼 디자인 import
// 기본 위젯 제공
import 'package:flutter/material.dart';

// 플러터 프로젝트 실행하는 함수
void main() {
  runApp(
  	// 머터리얼 디자인 위젯
  	MaterialApp(
    	// Scaffold 위젯
    	home: Scaffold(
        	// Text 위젯
        	body: Text(
            	'Hello World',    // 마지막 매개변수 끝에 콤마
            ),
         ),
     ),
  );
}

MaterialApp : 머터리얼 디자인 기반의 위젯을 사용

Scaffold 위젯 : 화면 전체를 차지. 레이아웃을 도와주고 UI 관련 특수 기능 제공(알림과 같은 스낵바 실행, 화면의 위에 앱바를 추가, 아래에 탭바를 추가 등)

※ MaterialApp 과 Scaffold 위젯을 추가하는 것이 기본 설정.

Text 위젯 : 글자를 화면에 출력하기 위해 사용.

 

실행

에뮬레이터를 선택하고 실행 버튼을 클릭한다.

 

에뮬레이터에서 결과 화면 확인.

- 화면 좌측 상단에 Hello World 글자가 보인다.(자세히 봐야 보인다. --;;)

기본 실행 완료!!

 

반응형
반응형

실제 기기 없이 에뮬레이터에서 테스트 하기 위한 방법!!

 

1. 안드로이드 에뮬레이트 생성

안드로이드 스튜디오에서 AVD Manager 버튼을 클릭.

 

좌측 하단의 Create Virtual Device... 버튼을 클릭

 

Category 에서 Phone 선택 후 Pixel 2 를 선택. Next 버튼 클릭.

 

OS는 API 33 을 선택!! Download 하면 된다. Next 버튼 클릭.

 

에뮬레이터 이름을 설정하고.. 좌측 하단의 Show Advanced Settings 버튼 클릭.

 

Internal Storage 를 넉넉하게 8GB 로 설정. Finish 클릭하여 완료한다.

 

에뮬레이터가 추가 된 것을 확인 할 수 있다.(기존에 API 30 은 삭제했다)

 

2. iOS 심뮬레이터는 Mac OS 환경에서 가능하므로... 패스... --;;

 

 

반응형
반응형

이제 직접 코딩하면서 배워보자!!

그동안 손이 근질근질.. 눈꺼풀이 천근만근이었을 것이다!!

 

1. 안드로이드 스튜디오에서 플러터 프로젝트 생성(GUI)

File > New > New Flutter Project...

 

Flutter Application 선택

 

Application 설정.

 - 소스 위치 : [플러터 프로젝트 디렉토리]\[플러터 프로젝트 명]

 - 플러터 SDK 설치 Path 설정

 

마지막으로 패키지 명을 입력 후 Finish

 

2. CLI 로 프로젝트 생성

$ flutter create hello_world2

....

All done!
You can find general documentation for Flutter at: https://docs.flutter.dev/
Detailed API documentation is available at: https://api.flutter.dev/
If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev

In order to run your application, type:

  $ cd hello_world2
  $ flutter run

Your application code is in hello_world2\lib\main.dart.

 

안드로이드 스튜디오에서 가져오기

 - CLI 로 생성한 hello_world2 디렉토리 Open

 

안드로이드 스튜디오에서 프로젝트 확인

 - hello_world\lib\main.dart 파일이 메인

 

반응형
반응형

다트는 동기/비동기 프로그래밍을 지원한다.

동기 : 요청을 하고 나서 응답이 올 때까지 기다렸다가 응답.

비동기 : 요청을 하고 나서 응답을 받지 않아도 다음 코드를 진행. 추후에 응답이 오면 처리.

 

Future

- Future 클래스 : 미래에 제너릭으로 값을 받아옴.

// 일정 시간 후 콜백 함수 실행.
// Future.delayed

void main() {
  print("실행 시작");
  Future.delayed(Duration(seconds: 3), (){
    print('3초 후 실행합니다.');
  });
  print("실행 완료?");
}

// 실행 결과(비동기로 동작)
실행 시작
실행 완료?
3초 후 실행합니다.

 

async, await

- 코드를 순서대로 실행시키기 위해 사용

void main() {
  addNumbers(1, 1);
}

// async 키워드는 함수 매개변수 정의와 바디 사이에 입력.
void addNumbers(int number1, int number2) async {
  print('$number1 + $number2 계산 시작!');

  // await는 대기하고 싶은 비동기 함수 앞에 입력.
  await Future.delayed(Duration(seconds: 3), (){
    print('$number1 + $number2 = ${number1 + number2}');
  });

  print('$number1 + $number2 코드 실행 끝');
}

// 실행 결과
1 + 1 계산 시작!
1 + 1 = 2
1 + 1 코드 실행 끝

 

비동기적으로 동작하는 지 확인

void main() {
  addNumbers(1, 1);
  addNumbers(2, 2);
}

Future<void> addNumbers(int number1, int number2) async {
  print('$number1 + $number2 계산 시작!');

  await Future.delayed(Duration(seconds: 3), (){
    print('$number1 + $number2 = ${number1 + number2}');
  });

  print('$number1 + $number2 코드 실행 끝');
}

// 실행 결과
1 + 1 계산 시작!
2 + 2 계산 시작!
1 + 1 = 2
1 + 1 코드 실행 끝
2 + 2 = 4
2 + 2 코드 실행 끝

 

두 함수를 순차적으로 실행하고 싶다면...

// main 함수에 async, await 사용.
void main() async {
  await addNumbers(1, 1);
  await addNumbers(2, 2);
}

Future<void> addNumbers(int number1, int number2) async {
  print('$number1 + $number2 계산 시작!');

  await Future.delayed(Duration(seconds: 3), (){
    print('$number1 + $number2 = ${number1 + number2}');
  });

  print('$number1 + $number2 코드 실행 끝');
}

// 실행 결과
1 + 1 계산 시작!
1 + 1 = 2
1 + 1 코드 실행 끝
2 + 2 계산 시작!
2 + 2 = 4
2 + 2 코드 실행 끝

 

결과 값을 비동기로 리턴 받고 싶다면...

void main() async {
  final result = await addNumbers(1, 1);
  print('결과값 $result');  // 일반 함수와 동일하게 반환값을 받을 수 있음
  final result2 = await addNumbers(2, 2);
  print('결과값 $result2');
}

Future<int> addNumbers(int number1, int number2) async {
  print('$number1 + $number2 계산 시작!');

  await Future.delayed(Duration(seconds: 3), (){
    print('$number1 + $number2 = ${number1 + number2}');
  });

  print('$number1 + $number2 코드 실행 끝');

  return number1 + number2;
}

//
1 + 1 계산 시작!
1 + 1 = 2
1 + 1 코드 실행 끝
결과값 2
2 + 2 계산 시작!
2 + 2 = 4
2 + 2 코드 실행 끝
결과값 4

 

 

Stream

- Future 는 결과값을 한번 리턴.

- 지속적으로 리턴받고 싶을 때는 Stream 을 사용.

- Stream 은 한번 Listen 하면 지속적으로 값을 받아온다.

import 'dart:async';

void main() {
  final controller = StreamController();  // StreamController 선언
  final stream = controller.stream;  // Stream 가져오기

  // Stream에 listen() 함수를 실행하면 값이 주입될 때마다 콜백 함수를 실행
  final streamListener1 = stream.listen((val) {
    print(val);
  });

  // Stream에 값을 주입할 때는 sink.add() 함수를 실행
  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);
  controller.sink.add(4);
}

// 실행 결과
1
2
3
4

 

Broadcasting

- Stream 을 여러번 Listen 하기

import 'dart:async';

void main() {
  final controller = StreamController();

  // 여러 번 listen할 수 있는 Broadcast Stream 객체 생성
  final stream = controller.stream.asBroadcastStream();

  // listen() 함수
  final streamListener1 = stream.listen((val) {
    print('listening 1');
    print(val);
  });

  // listen() 함수 추갸.
  final streamListener2 = stream.listen((val) {
    print('listening 2');
    print(val);
  });

  // add()를 실행할 때마다 listen()하는 모든 콜백 함수에 값이 주입
  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);

}

// 실행 결과.
listening 1
1
listening 2
1
listening 1
2
listening 2
2
listening 1
3
listening 2
3

 

함수로 Stream 반환하기

- 이건 잘 모르겠음. --;;

import 'dart:async';

// Stream을 반환하는 함수는 async*로 선언합니다.
Stream<String> calculate() async* {
  for (int i = 0; i < 5; i++) {
    // StreamController의 add()처럼 yield 키워드를 이용해서 값 반환
    yield 'i = $i';
    await Future.delayed(Duration(seconds: 1));
  }
}

void playStream() {
  // StreamController와 마찬가지로 listen() 함수로 콜백 함수 입력
  calculate().listen((val) {
    print(val);
  });
}

void main() {
  playStream();
}

// 실행 결과(1초에 한줄씩 출력)
i = 0
i = 1
i = 2
i = 3
i = 4

 

이번에는 비동기 프로그래밍에 대해서 알아봤다.

점점 어려워지는구만..ㅋㅋ

반응형
반응형

다트 언어는 객체지향 프로그래밍을 지원한다.

 

클래스

// class 정의.
class Person {

	// class 종속 변수
	String name = '이름';
    
    // class 종속 함수 = 메소드
    // 내부 속성을 사용할 때 this 키워드 사용.
    void sayName() {
    	print('My name is ${this.name}');
    }
    
}

 

인스턴스 생성

void main() {
	// 인스턴스 생성
	Person person = Person();
    
    // 메소드 실행.
    person.sayName();
}

 

생성자

class Person {
	// 생성자에서 입력 받는 변수들은 일반적으로 final 키워드 사용.
	final String name;
    
    // 생성자 선언. class 와 같은 이름.
    // 매개변수 지정
    Person(String name) : this.name = name;
    
    // this 를 사용할 경우.
    // 해당되는 변수에 자동을 매개변수가 저장.
    Person(this.name);
    
    void sayName() {
    	print('My name is ${this.name}');
    }
    
}

void main() {
	Persion person = Person('홍길동');
}

 

네임드 생성자

class Person {
	final String name;
    final int age;
    
    // 1개 이상의 변수를 저장할때는 , 기호로 연결.
    Person(String name, int age)
    	: this.name = name,
          this.age = age;
    
    // 네임드 생성자
    Person.fromMap(Map<String, dynamic> map)
    	: this.name = map['name'],
          this.age = map['age'];
    
    void sayName() {
    	print('My name is ${this.name}');
    }
    
}

void main() {
	Persion person = Person('홍길동', 20);
    Persion hong = Person.fromMap({
    	'name' : '홍길동',
        'age' : 20,
    });
}

 

private 변수

class Person {
	// '_' 로 시작하면 private 변수 선언.
	String _name;
    
    Person(this._name);
    
    void sayName() {
    	print('My name is ${this._name}');
    }
    
}

void main() {
	Persion person = Person('홍길동');
    print(person._name); // 같은 파일에서는 에러가 발생하지 않지만 다른 파일에서 사용시 에러 발생.
}

 

Getter / Setter

class Person {
	String _name = 'hong';
    
    // get 키워드를 사용하여 getter 명시
    // 매개변수를 받지 않음.
    String get name {
    	return this._name;
    }
    
    // set 키워드를 사용하여 setter 명시
    // 매개변수로 하나의 변수를 받음.
    set name(String name) {
    	this._name = name;
    }
    
}

void main() {
	Persion person = Person();
    person.name = '홍길동'; // setter
    print(person.name); // getter
}

 

상속

class Person {
	final String name;
    
    Person(this.name);
    
    void sayName() {
    	print('My name is ${name}.');
    }
}

// 상속
class Man extends Person {
	// 상속 받은 생성자
    // super 는 부모 클래스를 의미한다.
	Man(String name) : super(name);
    
    void sayMan() {
    	print('I am a man');
    }
}

void main() {
	Man man = Man('홍길동');
	print(man.sayName()); // 부모한테 물려받은 메소드
    print(man.sayMan()); // 자식이 추가한 메소드
}

 

오버라이드

class Person {
	final String name;
    
    Person(this.name);
    
    void sayName() {
    	print('My name is ${name}.');
    }
}

class Girl extends Person {
    // super 키워드를 직접 사용할 수 있음.
	Girl(super.name);
    
    @override
    void sayName() {
    	print('My name is ${name} and I am a girl');
    }
}

void main() {
	Girl girl = Girl('영희');
	print(girl.sayName()); // override 된 메소드
}

 

인터페이스

// implements 키워드를 사용하면 클래스를 인터페이스로 사용할 수 있다.
class Girl implements Person {
    final String name;
    
	Girl(super.name);
    
    // 인터페이스로 사용할 때는 모든 메소드를 재정의 해야한다.
    void sayName() {
    	print('My name is ${name}');
    }
}

void main() {
	Girl girl = Girl('영희');
	print(girl.sayName());
}

 

믹스인???

 - 이건 잘 모르겠다... --;;;

 

추상 클래스

 - 인스턴스화 할 필요가 없을 경우에 사용

// abstract 키워드를 사용하여 추상 클래스 정의.
abstract class Person {
	final String name;
    
    // 생성자 선언
    Person(this.name);
    
    // 추상 메소드 선언.
    void sayName();
}

// implements 키워드를 사용하여 추상 클래스를 구현.
// 생성자를 비롯하여 모든 메소드를 정의해야 한다.
class Girl implements Person {
	final String name;

    // super 키워드를 직접 사용할 수 있음.
	Girl(this.name);
    
    void sayName() {
    	print('My name is ${name} and I am a girl');
    }
}

void main() {
	Girl girl = Girl('영희');
    print(girl.sayName());
}

 

제너릭

// 제너릭
// 특정 변수의 타입을 제한하고 싶지 않을 때 사용.
// 인스턴스화 할 때 입력받을 타입을 T 로 지정.
class Generate<T> {
	// 데이터 타입을 인스턴스화 할 때 지정한 타입으로 사용.
	final T data;
    
    Generate({
    	required this.data,
    });
}

void main() {
	// 제너릭에 입력된 값을 통해 data 변수의 타입이 자동으로 유추.
	final gen = Generate<List<int>> (data : [1, 2, 3]);
    print(gen.data.reduce((v,e) => v + e); // 6
}

 

static

class Count {
	// static 은 클래스 자체에 귀속
	static int i = 0;
    
    // 생성자가 호출될때 마다 i 증가.
    Count() {
    	i++;
        print(i);
    }
}

void main() {
	Count c1 = Count(); // 1
    Count c2 = Count(); // 2
    Count c3 = Count(); // 3
}

 

Cascade

class Person {
	final String name;
    final int age;
    
    Person(this.name, this.age);
    
    void sayName() {
    	print('My name is ${this.name}');
    }
    
    void sayAge() {
    	print('My age is ${this.age}');
    }
}

void main() {
	// cascade 연산자 ..
    // 인스턴스의 속성이나 메소드를 연속해서 사용하는 것.
	Person person = Person('홍길동', 20)
    	..sayName()
        ..sayAge();
}

 

오늘은 다트의 객체지향 프로그래밍에 대해서 공부했다!!

 

반응형
반응형

플러터는 다트(Dart) 언어를 사용합니다.

다트를 알아야 플러터로 앱 개발이 가능하므로 다트를 공부해야합니다.

다트는 구글에서 개발한 프로그래밍 언어입니다.

다트는 모바일이나 데스크탑 기기를 타겟으로 하는 네이티브 플랫폼과 웹을 타켓으로 하는 웹 플랫폼으로 컴파일 할 수 있습니다.

다트 공부하기

1. 다트패드에서 공부하기

다트패드 홈페이지에서 코드 작성 및 실행 가능합니다.
 

DartPad

 

dartpad.dev

코드 작성 후 Run 버튼을 클릭하여 실행 결과를 확인 할 수 있다.

 

2. 안드로이드 스튜디오에서 공부하기

File -> New -> New Flutter Project ... 메뉴를 클릭하여 플러터 프로젝트를 생성한다.

lib/main.dart 파일에 코드를 작성 후 안드로이드 스튜디오 하단의 터미널 탭에서 dart lib/main.dart 명령어를 실행한다.

 

 

기초 문법

메인 함수

void main() {
	// 한줄 주석
    
    /* 시작기호, 끝 기호 */
    /* 여러 줄 주석
    * 
    *
    * */
    
    /// 슬래시 세 개를 사용하면 문서 주석을 작성할 수 있습니다.
    
}

 

변수 선언

// 변수 타입
String(문자열), int(정수형), double(실수형), bool(불리언 true/false)
String s = '';
int i = 1;
double d = 1.0;
bool b = true;

// 자동으로 타입을 추론. 한번 추론된 타입은 고정됨.
var name = '이름';

// 추후에 타입이 바뀌면 에러
name = 1; // 에러

// dynamic 키워드를 사용하면 변수 타입을 고정하지 않고 사용 가능.
dynamic d_name = '다이내믹';
d_name = 1;

// final, const 는 처음 선언 후 값 변경이 불가하다.
final String f_str = 'final string';
f_str = 'new string'; // 에러 발생

const String c_str = 'const string';
c_str = 'new const'; // 에러 발생

// final 은 런타임, const 는 빌드타임 상수이다.
// DateTime.now() 는 실행되는 순간(런타임 시) 값이 정해진다.
final DateTime f_now = DateTime.now();
const DateTime c_now = DateTime.now(); // 에러 발생

// 컬렉션
// List
List<String> list = ['s1', 's2', 's3'];
print(list[3]); // s3
list.add('s4');
final list_where = list.where((str) => str == 's1' || str == 's4'); // Iterable (s1, s4)
print(list_where.toList()); // [s1, s4]
final list_map = list.map((str) => 'new $str'); // Iterable (new s1, new s2, new s3, new s4);
final list_reduce = list.reduce((v, e) => v + ', ' + e); // (반환 타입이 String) s1, s2, s3, s4
final list_fold = list.fold(0, (v, e) => v + e.length); // (반환 타입 아무거나 가능) 8

// Map
Map<String, String> map = {
	'a' : 'a1',
    'b' : 'b2',
    'c' : 'c3',
};
print(map['b']); // b2
print(map.keys); // Iterable (a, b, c)
print(map.values); // Iterable (a1, b2, c3)

// Set
Set set = {'s1', 's2', 's3'};
set.contain('s2'); // true
set.toList(); // Set to List
Set.from(list); // Set from List

// enum
enum Status {
	ready,
    play,
    done,
}

Status status = Status.ready;
print(status); // Status.ready

 

연산자

// 사칙연산
+
-
*
/ // 몫
% // 나머지
++
--
+=
-=
*=
/=


// null 관련 연산자
// nallable
double? number1 = 1;

// not null
double number2 = null; // 에러 발생

// null 로 초기화
double? number;

// null 이면 할당.
number ??= 3; // 3

// not null 이면 할당하지 않음.
number ??= 4; // 3


// 값 비교
>
<
>=
<=
==
!=


// 타입 비교
print(number is int); // false
print(number is double); // true
print(number is! int); // true
print(number is! double); // false


// 논리 연산자
&&
||

 

제어문

if
else if
else

switch() {
	case 1 :
    	print();
    	break;
    default:
    	print();
}

for(int i=0; i < 10; i++) {
	print(i);
}

List<int> list = [1,2,3];
for(int n in list) {
	print(n);
}

while() {
}

do {
} while()

 

함수와 typedef

void main() {
	// 고정된 매개변수(포지셔널 파라미터)
      print(addTwoNumbers1(2, 3));

      // 이름이 있는 매개변수(네임드 파라미터)
      print(addTwoNumbers2(a:2, b:3));

      // 고정된 매개변수 기본값 지정
      print(addTwoNumbers3(2));
      print(addTwoNumbers3(2, 1));

      // 이름이 있는 매개변수 기본값 지정
      print(addTwoNumbers4(a:2));
      print(addTwoNumbers4(a:2, b:1));


      // typedef 는 일반적인 변수의 type 처럼 사용.
      Operation oper = add;
      oper(1, 2);

      oper = subtract;
      subtract(5, 2);

      calculate(3, 4, add);
}

// 함수의 시그니처 정의.
typedef Operation = void Function(int x, int y);

void add(int x, int y) {
  print('Add Result = ${x + y}');
}

void subtract(int x, int y) {
  print('Subtract Result = ${x - y}');
}

// dart 에서 함수는 일급 객체(일급 시민)이므로 함수를 값처럼 사용 가능.
// typedef 으로 선언한 함수를 매개변수로 사용 가능.
void calculate(int x, int y, Operation oper) {
  oper(x, y);
}

int addTwoNumbers1(int a, int b) {
  return a + b;
}

int addTwoNumbers2({required int a, required int b}) {
  return a + b;
}

int addTwoNumbers3(int a, [int b=3]) {
  return a + b;
}

int addTwoNumbers4({required int a, int b=3}) {
  return a + b;
}

 

기본 문법은 이정도로 정리하겠다.^^

반응형
반응형

본격적인 개발을 위한 도구인 안드로이드 스튜디오를 설치해보자.

공식 사이트에서 다운로드 한다.
 

Android 개발자  |  Android Developers

Android 앱 개발자를 위한 공식 사이트입니다. Android SDK 도구 및 API 문서를 제공합니다.

developer.android.com

 

그 다음 Flutter 플러그인을 설치한다.

File -> Settings 메뉴에서 Plugins 에서 flutter 검색 후 install 한다.

 

이제 개발을 위한 준비를 마쳤다.

본격적으로~~ 개발을 해보자!!

 

반응형

+ Recent posts