2023.5.21.(일)
Flutter
위 링크에 들어가보면, Flutter는 Google에서 개발 및 지원하는 오픈 소스 프레임워크입니다. 프런트엔드 및 풀 스택 개발자는 Flutter를 사용해 다수의 플랫폼에 대한 애플리케이션의 사용자 인터페이스(UI)를 단일 코드 베이스로 구축합니다.
라고 되어 있다. iOS, Android, 웹, Windows, MacOS, Linux의 여섯 가지 플랫폼에 대한 애플리케이션 개발을 지원한다.
네이티브 앱 개발에 보다 크로스 플랫폼 앱 개발에 더가까움
iOS와 같은 특정 플랫폼을 위한 애플리케이션을 코딩하는 것을 네이티브 앱 개발이라고 합니다. 이와는 대조적으로, 크로스 플랫폼 앱 개발은 단일 코드베이스를 기반으로 여러 플랫폼을 위한 애플리케이션을 구축하는 것입니다.
네이티브 앱 개발
네이티브 앱 개발에서 개발자는 특정 플랫폼용으로 코드를 작성하므로, 네이티브 디바이스 기능에 대한 전체 액세스 권한을 가집니다. 이는 일반적으로 크로스 플랫폼 앱 개발에 비해 더 높은 성능과 속도로 이어집니다. 반면, 여러 플랫폼에서 애플리케이션을 실행하려는 경우, 네이티브 앱 개발 방식에서는 더 많은 코드와 개발자가 필요합니다.
크로스 플랫폼 앱 개발
크로스 플랫폼 앱 개발을 통해 개발자는 하나의 프로그래밍 언어와 하나의 코드베이스를 사용하여 여러 플랫폼용 애플리케이션을 구축할 수 있습니다. 여러 플랫폼용으로 애플리케이션을 출시하는 경우 크로스 플랫폼 앱 개발 방식이 네이티브 앱 개발 방식보다 비용과 시간이 적게 듭니다.
Flutter 의 이점
- 네이티브에 가까운 성능: Flutter는 프로그래밍 언어로
Dart
를 사용하고 기계 코드로 컴파일 합니다. 호스트 디바이스가 이 코드를 이해하므로 빠르고 효과적인 성능이 보장됩니다. - 빠르고 일관적이며 사용자 지정이 가능한 렌더링: Flutter는 플랫폼별 렌더링 도구를 사용하지 않고, Google의 오픈 소스 Skia 그래픽 라이브러리를 사용하여 UI를 렌더링합니다. 따라서 애플리케이션에 액세스하는 데 사용하는 플랫폼에 관계 없이 사용자에게 일관된 시각적 경험을 제공합니다.
- 개발자에게 편리한 도구: Google은 사용 편의성에 중점을 두고 Flutter를 만들었습니다. 개발자는 핫 리로드와 같은 도구를 사용하여 상태를 바꾸지 않고 코드 변경 내용을 미리 볼 수 있습니다. 위젯 검사기와 같은 다른 도구를 사용하면 UI 레이아웃 문제를 손쉽게 시각화하고 해결할 수 있습니다.
Flutter의 위젯이란 무엇인가요?
Flutter에서 개발자는 위젯을 사용하여 UI 레이아웃을 만듭니다. 즉, 창과 패널부터 버튼과 텍스트에 이르기까지, 사용자가 화면에서 보는 모든 요소가 위젯으로 만들어집니다.
Flutter 위젯은 개발자가 손쉽게 사용자 지정할 수 있도록 설계되었습니다. Flutter는 구성 접근 방식을 통해 이를 실현합니다. 즉, 대부분의 위젯은 작은 위젯으로 구성되며, 가장 기본적인 위젯은 특정한 용도가 있습니다. 따라서 개발자가 위젯을 결합하거나 편집하여 새 위젯을 만들 수 있습니다.
Flutter는 플랫폼의 기본 제공 위젯을 사용하는 것이 아니라, 자체 그래픽 엔진을 사용하여 위젯을 렌더링합니다. 덕분에 사용자는 플랫폼 전체에 걸쳐 Flutter 애플리케이션에서 유사한 모양과 느낌을 경험할 수 있습니다. 일부 Flutter 위젯은 플랫폼별 위젯에서는 수행할 수 없는 기능을 수행할 수 있으므로, 이러한 접근 방식은 개발자에게 유연성을 제공합니다.
Flutter 위젯의 유형
Flutter는 다운로드 할 때부터 광범위한 위젯 카탈로그와 함께 제공됩니다. 카탈로그에는 스타일링, Cupertino(iOS 스타일 위젯) 및 Material Components(Google의 재료 설계 지침을 따르는 위젯)를 비롯한 14개의 범주가 있습니다.
Flutter에는 레이아웃과 테마도 포함되어 있어 개발자가 빠르게 구축하는 데 도움이 됩니다.
AWS는 Flutter를 어떻게 지원하나요?
Flutter는 애플리케이션에서 사용자에게 표시되는 부분을 만드는 데 도움이 됩니다. 하지만 애플리케이션을 개발하는 데에는 인증, 파일 스토리지, 분석 등 사용자에게 보이지 않는 많은 기능이 필요합니다. AWS Amplify와 Amplify Flutter는 바로 여기에 사용됩니다.
AWS Amplify는 안전하고 확장 가능한 모바일 및 웹 애플리케이션을 구축할 수 있는 프레임워크입니다. iOS, Android, 웹, React Native 및 Flutter 를 지원하는 AWS Amplify 를 사용하면 AWS 기반의 애플리케이션을 쉽고 빠르게 구축할 수 있습니다.
Amplify Flutter는 Flutter 애플리케이션의 백엔드를 프로비저닝, 빌드 및 배포할 수 있는 도구와 라이브러리의 집합입니다. Amplify Flutter를 사용하여 Flutter 애플리케이션을 AWS에 연결하고 일반적인 백엔드 요구 사항을 해결할 수 있습니다.
Amplify Flutter를 백엔드 솔루션으로 사용
-
분석: Amazon Pinpoint에서 사용자에 대한 추적 데이터를 수집할 수 있습니다. 이벤트를 손쉽게 기록하고 필요에 따라 지표와 속성을 사용자 지정할 수 있습니다.
-
API: GraphQL API는 백엔드의 데이터를 검색하는 데 유용하며 AWS AppSync에서 지원됩니다.
-
인증: 사용자를 인증하고 등록 및 로그인 양식뿐만 아니라 다중 인증도 구현할 수 있습니다. 백그라운드에서 다른 Amplify 범주에 필요한 권한을 제공합니다. 사용을 시작하는 순간부터 Cognito 사용자 풀과 ID 풀을 지원합니다.
-
데이터 스토어: 오프라인 및 온라인 시나리오를 위한 추가 코드를 작성하지 않고도 분산 공유 데이터를 사용할 수 있습니다. 따라서 분산된 사용자 간 데이터에 대해서도 로컬 전용 데이터 만큼 간편하게 작업에 사용할 수 있습니다. Amplify DataStore는 데이터를 자동으로 버저닝하고 AppSync를 사용하여 클라우드에서 충돌 감지 및 해결 기능을 구현합니다.
-
스토리지: Amplify Flutter를 사용하면 스토리지의 객체를 업로드하고, 다운로드하고, 삭제할 수 있습니다.
외부 데이터를 fetch 할 때.
import 'package:best_friend/api/data_of_articles.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:html/parser.dart' as parser;
import 'package:best_friend/api/article_model.dart';
import 'package:url_launcher/url_launcher.dart';
class Articles extends StatefulWidget {
final String link, queryString;
const Articles({super.key, required this.link, required this.queryString});
@override
State<Articles> createState() => _ArticlesState();
}
class _ArticlesState extends State<Articles> {
List<Article> articles = [];
@override
void initState() {
// TODO: implement initState
super.initState();
getWebsiteData();
}
Future<void> getWebsiteData() async {
print(widget.link);
final url = Uri.parse(widget.link);
final response = await http.get(url);
final document = parser.parse(response.body);
//dom.Document html = dom.Document.html(response.body);
final mainTitles = document
.querySelectorAll(widget.queryString)
.map((element) => element.innerHtml.trim())
.toList();
final links = document.querySelectorAll('td.td_subject.text-left > a');
List<String> titles = [];
List<String> detailLink = [];
print('Count: ${mainTitles.length}');
for (var title in mainTitles) {
var doc = parser.parseFragment(title);
doc.querySelectorAll('span').forEach((span) => span.remove());
String docString = doc.text!.replaceAll('[]', '');
titles.add(docString);
}
for (var link in links) {
detailLink.add(link.attributes['href']!);
print(link.attributes['href']);
}
setState(() {
articles = List.generate(
titles.length,
(index) =>
Article(url: detailLink[index], title: titles[index], urlImage: ''),
);
DataOfArticles list = DataOfArticles();
DataOfArticles.listArticles = articles;
});
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 150,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: articles.length,
separatorBuilder: (context, index) {
return const SizedBox(
width: 15,
);
},
itemBuilder: (context, index) {
final article = articles[index];
return GestureDetector(
onTap: () => launchUrl(Uri.parse(articles[index].url)),
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 10,
offset: const Offset(0, 3),
),
],
),
width: 150,
child: ListTile(
title: Text(
article.title,
style: const TextStyle(
fontSize: 15, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
),
),
);
}),
);
}
}
로그인할 때
import 'package:flutter/material.dart';
import 'package:best_friend/main_screens/main_community.dart';
class AuthPage extends StatelessWidget {
AuthPage({Key? key}) : super(key: key);
final GlobalKey<FormState> _formkey = GlobalKey<FormState>();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
title: const Text(
"로그인",
style: TextStyle(
fontSize: 27,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () {},
),
),
body: Stack(alignment: Alignment.center, children: <Widget>[
Container(color: Colors.white),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Container(
width: 200, height: 200, color: Colors.blue), // 로고 넣을 자리
SizedBox(
width: 850,
child: Stack(
children: <Widget>[
_inputForm(size),
_authButton(size, context),
],
),
),
Container(
height: size.height * 0.1,
),
const Text("회원가입 버튼 text는 추후 수정"),
Container(
height: size.height * 0.05,
),
],
),
]));
}
Widget _authButton(Size size, BuildContext context) {
return Positioned(
left: 100,
right: 100,
bottom: 0,
child: SizedBox(
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
),
onPressed: () {
if (_formkey.currentState?.validate() != null) {
//
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const CommunityScreen()),
);
}
},
child: const Text(
"로그인",
style: TextStyle(fontSize: 20, color: Colors.white),
),
),
),
);
}
Widget _inputForm(Size size) {
return Padding(
padding: EdgeInsets.all(size.width * 0.05),
child: Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 6,
child: Padding(
padding:
const EdgeInsets.only(left: 12, right: 12, top: 12, bottom: 32),
child: Form(
key: _formkey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
icon: Icon(Icons.account_circle),
labelText: "Email",
),
validator: (String? value) {
if (value == null) {
return "이메일 주소를 입력해주세요";
}
return null;
},
),
TextFormField(
obscureText: true,
controller: _passwordController,
decoration: const InputDecoration(
icon: Icon(Icons.vpn_key),
labelText: "Password",
),
validator: (String? value) {
if (value == null) {
return "비밀번호를 입력해주세요";
}
return null;
},
),
Container(
height: 8,
),
const Text("비밀번호를 잊으셨나요? 비밀번호 찾기")
])),
),
),
);
}
}
구글 맵 API 가져오기
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:best_friend/appBar/BFAppBar.dart';
//https://kitty-geno.tistory.com/46
// api key:
class MapScreen extends StatefulWidget {
const MapScreen({super.key});
@override
State<MapScreen> createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
final String apiKey = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const BFAppBar(
appBarFunction: 3,
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 30),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: SizedBox(
width: 620,
height: 850,
child: InAppWebView(
initialUrlRequest: URLRequest(
url: Uri.parse('https://maps.google.com/maps?key=$apiKey'),
),
),
),
),
),
);
}
}
특정 키워드로 검색하기
import 'package:best_friend/api/article_model.dart';
import 'package:best_friend/api/data_of_articles.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class Search extends StatefulWidget {
const Search({Key? key}) : super(key: key);
@override
State<Search> createState() => _SearchState();
}
class _SearchState extends State<Search> {
String _searchText = "";
final List<Article> dataList = DataOfArticles.listArticles;
List<Article> filteredList = [];
void _filterList() {
filteredList = dataList
.where((article) =>
article.title.toLowerCase().contains(_searchText.toLowerCase()))
.toList();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
'검색',
style: TextStyle(
fontSize: 27,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
backgroundColor: Colors.blue,
actions: const [],
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
onChanged: (value) {
setState(() {
_searchText = value;
_filterList();
});
},
decoration: const InputDecoration(
labelText: "Search",
prefixIcon: Icon(Icons.search),
),
),
),
if (_searchText.isNotEmpty) ...[
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'검색 결과...',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
const SizedBox(height: 10),
for (var i = 0; i < filteredList.length; i++) ...[
GestureDetector(
onTap: () => launchUrl(Uri.parse(filteredList[i].url)),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Container(
width: 450,
height: 60,
decoration: BoxDecoration(
color: const Color.fromARGB(255, 236, 247, 254),
borderRadius: BorderRadius.circular(5),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 10,
offset: const Offset(0, 3),
),
],
),
// Your container code...
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Container(
child: Text(
'${i + 1}. ${filteredList[i].title}',
style: const TextStyle(
fontSize: 15,
),
textAlign: TextAlign.start,
),
),
),
),
),
),
),
const SizedBox(height: 20), // Increased spacing between items
],
],
],
),
),
);
}
}
TextField. 입력을 받을 때
import 'package:flutter/material.dart';
class Input extends StatelessWidget {
final double sizeOfBox;
final String category;
const Input({super.key, this.sizeOfBox = 130, required this.category});
// 이 함수에서 입력된 값을 handling 함.
void textChanged(String value) {
print(value);
}
@override
Widget build(BuildContext context) {
return Row(
children: [
Text(
category,
style: const TextStyle(
color: Colors.black, fontSize: 30, fontWeight: FontWeight.bold),
),
const SizedBox(
width: 70,
),
// TexField 를 클래스로 만들 것.
SizedBox(
width: sizeOfBox,
child: TextField(
onChanged: textChanged,
decoration: InputDecoration(
labelText: '$category를 입력하세요',
hintText: ':',
),
),
),
//BFInputField(nameInputField: '나이를 입력하세요'),
],
);
}
}