로메오의 블로그

Flutter 본문

App & OS/Hybrid

Flutter

romeoh 2024. 8. 18. 15:50
반응형
$ brew --version
Homebrew 4.3.17

$ brew info openssl@1.1
==> openssl@1.1: stable 1.1.1w (bottled) [keg-only]

$ rvm --version
rvm 1.29.12 (latest) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin [https://rvm.io]

$ ruby --version
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [arm64-darwin23]

$ pod --version
1.15.2

 

 

 

 

Navigator.push

main.dart

import 'package:flutter/material.dart';
import 'home_screen.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Navigation Example',
      home: HomeScreen(),
    );
  }
}

 

 

 

home_screen.dart

import 'package:flutter/material.dart';
import 'second_screen.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Screen'),
      ),

      // body
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            /**
             *  1. Navigation Push
             */
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => const SecondScreen()),
                );
              },
              child: const Text('1. Navigation Push'),
            ),
            const SizedBox(height: 20),

            /**
             * 2. Full-size popover
             */
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  PageRouteBuilder(
                    opaque: false,
                    pageBuilder: (BuildContext context, _, __) =>
                        const SecondScreen(),
                    transitionsBuilder:
                        (_, Animation<double> animation, __, Widget child) {
                      return FadeTransition(
                        opacity: animation,
                        child: child,
                      );
                    },
                  ),
                );
              },
              child: const Text('2. Full-Screen Popup'),
            ),
            const SizedBox(height: 20),

            /**
             * 3. Dialog
             */
            ElevatedButton(
              onPressed: () {
                showDialog(
                  context: context,
                  builder: (BuildContext context) {
                    return Dialog(
                      backgroundColor: Colors.transparent,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(20.0),
                      ),
                      child: SizedBox(
                        width: MediaQuery.of(context).size.width * 0.8,
                        height: 400, // 원하는 높이로 설정
                        child: const SecondScreen(),
                      ),
                    );
                  },
                );
              },
              child: const Text('3. Dialog Screen'),
            ),
            const SizedBox(height: 20),

            /**
             * 4. 파라미터 전달
             */
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) =>
                        const SecondScreen(data: 'Hello from HomeScreen!'),
                  ),
                );
              },
              child: const Text('4. 파라미터 전달'),
            ),
            const SizedBox(height: 20),

            /**
             * 5. print
             */
            ElevatedButton(
              onPressed: () {
                // 콘솔에 메시지 출력
                print('debug Message');
              },
              child: const Text('5. Print'),
            ),
            const SizedBox(height: 20),

            /**
             * 6. Snack bar
             */
            ElevatedButton(
              onPressed: () {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(
                    content: Text('SnackBar Message'),
                  ),
                );
              },
              child: const Text('6. Show SnackBar'),
            ),
            const SizedBox(height: 20),

            /**
             * 7. Stack 제거
             */
            ElevatedButton(
              onPressed: () {
                Navigator.pushAndRemoveUntil(
                  context,
                  MaterialPageRoute(builder: (context) => const SecondScreen()),
                  (Route<dynamic> route) => false, // 모든 기존 화면을 제거
                );
              },
              child: const Text('7. Stack 제거'),
            ),
            const SizedBox(height: 20),
          ],
        ),
      ),
    );
  }
}

 

 

 

second_screen.dart

import 'package:flutter/material.dart';

class SecondScreen extends StatelessWidget {
  final String? data;

  const SecondScreen({super.key, this.data});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.transparent,
      body: Center(
        child: Container(
          width: double.infinity,
          height: double.infinity,
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(20.0),
          ),
          padding: const EdgeInsets.all(20.0),

          // child
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                data ?? 'This is Second Screen',
                style: const TextStyle(
                    fontSize: 24.0, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: const Text('Close'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

 

 

 

 

 

 

 

 

 

 

image picker 사용하기

dependencies:
  flutter:
    sdk: flutter
  image_picker: ^0.8.4+8

pubspec.yaml 에 image_picker 패키지를 추가합니다.

 

 

 

$ flutter pub get

패키지를 설치합니다.

 

 

 

 

AndroidManifest.xml

your_flutter_project/
├── android/
│   ├── app/
│   │   ├── src/
│   │   │   ├── main/
│   │   │   │   └── AndroidManifest.xml

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

접근 권한을 추가합니다.

 

 

 

Info.plist

your_flutter_project/
├── ios/
│   ├── Runner/
│   │   └── Info.plist

<key>NSPhotoLibraryUsageDescription</key>
<string>사진첩 접근을 허용해주세요.</string>

 

 

 

 

 

 

 

 

다국어

dependencies:
  flutter:
    sdk: flutter
  image_picker: ^0.8.4+8
  flutter_localizations:
    sdk: flutter
  intl: ^0.19.0

  cupertino_icons: ^1.0.8

dev_dependencies:
  flutter_test:
    sdk: flutter
  intl_utils: ^2.0.0

 

 

 

 

 

lib > l10n > intl_en.arb

lib > l10n > intl_ko.arb

파일을 생성합니다.

 

 

 

intl_en.arb
{
  "hello": "Hello",
  "welcome": "Welcome to Flutter",
  "Home_Screen": "Home Screen 1"
}



intl_ko.arb
{
  "hello": "안녕하세요",
  "welcome": "플러터에 오신 것을 환영합니다",
  "Home_Screen": "홈 스크린 1"
}

 

 

 

 

 

 

$ flutter pub run intl_utils:generate

arb 파일을 generate 합니다.

 

 

 

 

lib > generated 폴더에 dart 파일이 generate 됩니다.

 

 

 

 

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'generated/l10n.dart';
import 'home_screen.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // locale: const Locale('en'),
      localizationsDelegates: const [
        S.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: S.delegate.supportedLocales,
      localeResolutionCallback: (locale, supportedLocales) {
        for (var supportedLocale in supportedLocales) {
          if (supportedLocale.languageCode == locale?.languageCode) {
            return supportedLocale;
          }
        }
        return supportedLocales.first;
      },
      title: 'Navigation Example',
      home: HomeScreen(),
    );
  }
}

 

 

 

 

 

 

home_screen.dart

...
import 'generated/l10n.dart';

class HomeScreen extends StatefulWidget {
  ...
}

class _HomeScreenState extends State<HomeScreen> {
  ...

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(S.of(context).Home_Screen),
      ),

      ...
    );
  }
}

 

 

 

 

 

 

http 통신

dependencies:
  flutter:
    sdk: flutter
  image_picker: ^0.8.4+8
  flutter_localizations:
    sdk: flutter
  intl: ^0.19.0
  http: ^0.13.3

 

 

 

http_screen.dart

// ignore_for_file: avoid_print
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'models/post.dart';

class HttpScreen extends StatefulWidget {
  const HttpScreen({super.key});

  @override
  // ignore: library_private_types_in_public_api
  _HttpPageState createState() => _HttpPageState();
}

class _HttpPageState extends State<HttpScreen> {
  List<Post> _posts = [];
  bool _isLoading = false;

  Future<void> fetchPosts() async {
    setState(() {
      _isLoading = true;
    });

    final response =
        await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));

    if (response.statusCode == 200) {
      List<dynamic> body = json.decode(response.body);
      List<Post> posts =
          body.map((dynamic item) => Post.fromJson(item)).toList();

      setState(() {
        _posts = posts;
        _isLoading = false;
      });
    } else {
      setState(() {
        _isLoading = false;
      });
      throw Exception('Failed to load posts');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('http 통신'),
        ),

        // body
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ElevatedButton(
                onPressed: () {
                  fetchPosts();
                },
                child: const Text('Fetch Data'),
              ),
              const SizedBox(height: 20),
              Expanded(
                child: _isLoading
                    ? Center(child: CircularProgressIndicator())
                    : ListView.builder(
                        itemCount: _posts.length,
                        itemBuilder: (context, index) {
                          Post post = _posts[index];
                          return ListTile(
                            title: Text(post.title),
                            subtitle: Text(post.body),
                          );
                        },
                      ),
              )
            ],
          ),
        ));
  }
}

 

 

 

libs > models > post.dart

class Post {
  final int id;
  final String title;
  final String body;

  Post({required this.id, required this.title, required this.body});

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'],
      title: json['title'],
      body: json['body'],
    );
  }
}

 

 

 

 

 

 

사진업로드

// ignore_for_file: avoid_print

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:http/http.dart' as http;
import 'dart:io';

class GalleryScreen extends StatefulWidget {
  const GalleryScreen({super.key});

  @override
  // ignore: library_private_types_in_public_api
  _GalleryPageState createState() => _GalleryPageState();
}

class _GalleryPageState extends State<GalleryScreen> {
  File? _image;

  Future<void> _pickImage() async {
    final picker = ImagePicker();
    final pickedFile = await picker.pickImage(source: ImageSource.gallery);

    if (pickedFile != null) {
      setState(() {
        _image = File(pickedFile.path);
      });
    }
  }

  Future<void> _uploadImage() async {
    if (_image == null) {
      print('No image to upload.');
      return;
    }

    String uploadUrl =
        'https://yoursite.com/upload.php'; // 서버의 업로드 URL

    try {
      var request = http.MultipartRequest('POST', Uri.parse(uploadUrl));
      request.files
          .add(await http.MultipartFile.fromPath('image', _image!.path));

      var response = await request.send();

      if (response.statusCode == 200) {
        print('Image uploaded successfully.');
        var responseData = await response.stream.toBytes();
        var responseString = String.fromCharCodes(responseData);
        print(responseString);
      } else {
        print('Image upload failed with status: ${response.statusCode}');
      }
    } catch (e) {
      print('Error uploading image: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('사진 불러오기'),
        ),

        // body
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              _image == null ? const Text('이미지를 선택하세요.') : Image.file(_image!),
              const SizedBox(height: 20),

              /**
               * 사진 불러오기
               */
              ElevatedButton(
                onPressed: _pickImage,
                child: const Text('사진첩에서 이미지 선택'),
              ),
              const SizedBox(height: 20),

              /**
               * 사진 전송
               */
              ElevatedButton(
                onPressed: _uploadImage,
                child: const Text('사진 전송'),
              ),
            ],
          ),
        ));
  }
}

 

 

 

upload.php

<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    if (isset($_FILES['image'])) {
        $file = $_FILES['image'];
        $upload_directory = __DIR__ . '/uploads/';
        $filename = basename($file['name']);
        $target_path = $upload_directory . $filename;
        
        // 업로드 디렉토리 쓰기 가능 여부 확인
        if (!is_writable($upload_directory)) {
            echo json_encode(array('status' => 'error', 'message' => 'Upload directory is not writable.'));
            exit;
        }
        
        // 파일 이동 시도
        if (move_uploaded_file($file['tmp_name'], $target_path)) {
            echo json_encode(array('status' => 'success', 'message' => 'File uploaded successfully.'));
        } else {
            echo json_encode(array('status' => 'error', 'message' => 'Failed to move uploaded file.'));
            // 오류 메시지를 출력
            $error = error_get_last();
            echo json_encode(array('status' => 'error', 'error' => $error));
        }
        
    } else {
        echo json_encode(array('status' => 'error', 'message' => 'No file uploaded.'));
    }
} else {
    echo json_encode(array('status' => 'error', 'message' => 'Invalid request method.'));
}
?>

php version 5.3

 

 

 

 

 

 

 

 

 

 

 

반응형
Comments