Skip to content
Snippets Groups Projects
Commit bad2f718 authored by 권 세빈's avatar 권 세빈
Browse files

final

parent 5e195149
No related branches found
No related tags found
No related merge requests found
# foss-2024-2-final
# Software Tool Time - Flutter Flow 사용 가이드
## 아이템 선정 동기
최근 모바일 애플리케이션 개발 분야에서 크로스 플랫폼 개발의 중요성이 날로 증가하고 있습니다. 특히 Flutter는 Google이 개발한 UI 프레임워크로서, 단일 코드베이스로 iOS, Android, Web, Desktop 등 다양한 플랫폼의 애플리케이션을 개발할 수 있다는 강력한 장점을 가지고 있습니다. 많은 학생들이 프로그래밍 언어 학습의 높은 진입 장벽, 복잡한 개발 환경 설정, UI/UX 구현의 어려움, 백엔드 서비스 연동의 복잡성, 크로스 플랫폼 개발 시 발생하는 호환성 문제, 디자인 시스템 구축과 관리의 어려움, 상태 관리와 데이터 흐름 제어의 복잡성 등의 문제를 겪고 있습니다.
## Getting started
## Flutter Flow 소개
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
이러한 상황에서 Flutter Flow라는 No-Code 개발 플랫폼은 기존의 진입 장벽을 크게 낮출 수 있는 훌륭한 대안이 될 수 있다고 판단하여 이번 프로젝트의 주제로 선정하게 되었습니다. Flutter Flow는 드래그 앤 드롭 방식의 직관적인 인터페이스를 통해 복잡한 코드 작성 없이도 전문적인 수준의 UI를 구현할 수 있게 해주며, Firebase와의 긴밀한 통합을 통해 백엔드 기능까지 손쉽게 구현할 수 있는 장점을 가지고 있습니다.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## 개발 과정의 어려움
## Add your files
하지만 프로젝트를 진행하는 과정에서 몇 가지 예상치 못한 어려움에 직면하게 되었습니다. 가장 큰 문제는 Firebase의 Firestore 서비스가 무료 버전에서 유료 버전으로 정책이 변경된 것이었습니다. 당초 계획했던 실시간 데이터베이스를 활용한 로그인 기능 구현 데모를 직접 보여주지 못하게 되어, 대신 Flutter Flow에서 제공하는 템플릿을 활용하여 기능의 작동 방식을 설명하는 것으로 대체해야 했습니다. 이는 학습 효과 면에서 다소 아쉬운 부분이었으며, 실제 개발 과정에서 발생할 수 있는 문제 해결 과정을 보여주지 못했다는 한계가 있었습니다.
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
## 제약사항과 한계점
```
cd existing_repo
git remote add origin https://git.ajou.ac.kr/seebeen/foss-2024-2-final.git
git branch -M main
git push -uf origin main
```
또 다른 중요한 제약사항은 무료 버전에서는 생성된 코드를 추출하여 로컬 환경에서 실행하는 기능이 제한된다는 점이었습니다. 이로 인해 Flutter Flow에서 생성된 코드를 실제 개발 환경으로 가져와 커스터마이징하는 과정을 시연하지 못했습니다. 이는 특히 더 복잡한 기능을 구현하거나 세부적인 조정이 필요한 경우에 중요한 부분이었기에, 완전한 개발 워크플로우를 보여주지 못한 것이 아쉬움으로 남았습니다.
## Integrate with your tools
## 현재 개발 교육의 한계와 No-Code 도구의 가능성
- [ ] [Set up project integrations](https://git.ajou.ac.kr/seebeen/foss-2024-2-final/-/settings/integrations)
현재 소프트웨어 개발 교육에서는 종종 도구의 사용법보다는 프로그래밍 언어의 문법과 알고리즘에 초점을 맞추는 경향이 있습니다. 하지만 Flutter Flow와 같은 No-Code 도구들은 개발의 진입 장벽을 낮추고, 학생들이 자신의 아이디어를 빠르게 현실화해볼 수 있는 기회를 제공합니다. 이는 결과적으로 더 많은 학생들이 앱 개발에 흥미를 가지고 도전해볼 수 있는 계기가 될 것입니다.
## Collaborate with your team
## 향후 발전 방향과 기대사항
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
앞으로 Flutter Flow와 같은 No-Code 플랫폼들이 더욱 발전하여, 현재의 한계점들이 개선되기를 기대합니다. 특히 무료 버전에서도 기본적인 백엔드 기능들을 테스트해볼 수 있고, 생성된 코드를 자유롭게 활용할 수 있게 된다면, 교육적인 측면에서 더욱 가치 있는 도구가 될 것입니다. 이번 프로젝트가 앱 개발에 관심이 있는 학우들에게 새로운 가능성을 보여주는 계기가 되었기를 희망하며, 앞으로도 이러한 혁신적인 개발 도구들을 지속적으로 탐구하고 공유하는 것이 중요하다고 생각합니다.
## Test and Deploy
## Flutter Flow의 강점과 활용 가능성
Use the built-in continuous integration in GitLab.
그럼에도 불구하고, 이번 프로젝트를 통해 Flutter Flow가 가진 강력한 장점들을 확인할 수 있었습니다. 특히 시각적 개발 환경을 통한 직관적인 UI 구현, 실시간 미리보기 기능, 컴포넌트 기반의 설계 방식은 개발 입문자들의 학습 곡선을 크게 완화시킬 수 있는 요소들이었습니다. 또한 버전 관리 기능을 통해 개발 과정에서의 변경 사항을 쉽게 추적하고 관리할 수 있다는 점도 매우 유용했습니다.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
## 프로젝트를 통한 교훈과 향후 계획
***
이러한 경험을 통해 학습한 내용들은 향후 다른 프로젝트에서도 유용하게 활용될 수 있을 것입니다. 특히 프로토타입 제작 단계에서 Flutter Flow를 활용하여 빠르게 디자인을 검증하고, 이후 필요에 따라 네이티브 개발로 전환하는 하이브리드 접근 방식은 효율적인 개발 전략이 될 수 있을 것입니다. 또한 이번 경험은 새로운 도구나 기술을 도입할 때 발생할 수 있는 다양한 제약사항들을 미리 고려하고 대비하는 것의 중요성도 일깨워주었습니다.
# Editing this README
## 결론
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
최종적으로, Flutter Flow는 현재 일부 제한사항에도 불구하고 앱 개발 입문자들에게 매우 유용한 도구가 될 수 있습니다. 직관적인 개발 환경을 통해 실제 앱 개발 과정을 경험할 수 있으며, 이는 추후 전통적인 개발 방식으로 전환할 때도 큰 도움이 될 것입니다. 이번 프로젝트를 통해 얻은 경험과 교훈이 앞으로 더 많은 학생들이 앱 개발에 도전하는 데 도움이 되기를 희망합니다.
import '/auth/firebase_auth/auth_util.dart';
import '/components/custom_appbar_widget.dart';
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/flutter_flow/flutter_flow_widgets.dart';
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import 'sign_in_model.dart';
export 'sign_in_model.dart';
class SignInWidget extends StatefulWidget {
const SignInWidget({super.key});
@override
State<SignInWidget> createState() => _SignInWidgetState();
}
class _SignInWidgetState extends State<SignInWidget> {
late SignInModel _model;
final scaffoldKey = GlobalKey<ScaffoldState>();
late StreamSubscription<bool> _keyboardVisibilitySubscription;
bool _isKeyboardVisible = false;
@override
void initState() {
super.initState();
_model = createModel(context, () => SignInModel());
logFirebaseEvent('screen_view', parameters: {'screen_name': 'SignIn'});
if (!isWeb) {
_keyboardVisibilitySubscription =
KeyboardVisibilityController().onChange.listen((bool visible) {
safeSetState(() {
_isKeyboardVisible = visible;
});
});
}
_model.emailAddressTextController ??= TextEditingController();
_model.emailAddressFocusNode ??= FocusNode();
_model.passwordTextController ??= TextEditingController();
_model.passwordFocusNode ??= FocusNode();
WidgetsBinding.instance.addPostFrameCallback((_) => safeSetState(() {
_model.emailAddressTextController?.text = 'tsmith@email.com';
_model.passwordTextController?.text = 'password';
}));
}
@override
void dispose() {
_model.dispose();
if (!isWeb) {
_keyboardVisibilitySubscription.cancel();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
FocusManager.instance.primaryFocus?.unfocus();
},
child: Scaffold(
key: scaffoldKey,
backgroundColor: FlutterFlowTheme.of(context).primaryBackground,
body: SafeArea(
top: true,
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Padding(
padding: EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
wrapWithModel(
model: _model.customAppbarModel,
updateCallback: () => safeSetState(() {}),
child: CustomAppbarWidget(
backButton: true,
actionButton: false,
optionsButton: false,
actionButtonAction: () async {},
optionsButtonAction: () async {},
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(0, 24, 0, 0),
child: Text(
'Sign In',
style: FlutterFlowTheme.of(context)
.displaySmall
.override(
fontFamily: 'Inter',
letterSpacing: 0.0,
),
),
),
Form(
key: _model.formKey,
autovalidateMode: AutovalidateMode.disabled,
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0, 18, 0, 0),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
0, 0, 0, 4),
child: Text(
'Email',
style: FlutterFlowTheme.of(context)
.bodyMedium
.override(
fontFamily: 'Inter',
letterSpacing: 0.0,
fontWeight: FontWeight.w600,
),
),
),
TextFormField(
controller:
_model.emailAddressTextController,
focusNode: _model.emailAddressFocusNode,
autofocus: false,
autofillHints: [AutofillHints.email],
textInputAction: TextInputAction.next,
obscureText: false,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0x00000000),
width: 1.0,
),
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0x00000000),
width: 1.0,
),
borderRadius: BorderRadius.circular(8),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0x00000000),
width: 1.0,
),
borderRadius: BorderRadius.circular(8),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0x00000000),
width: 1.0,
),
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: FlutterFlowTheme.of(context)
.secondaryBackground,
),
style: FlutterFlowTheme.of(context)
.bodyMedium
.override(
fontFamily: 'Inter',
fontSize: 16,
letterSpacing: 0.0,
fontWeight: FontWeight.w500,
lineHeight: 1,
),
minLines: 1,
keyboardType: TextInputType.emailAddress,
cursorColor:
FlutterFlowTheme.of(context).primary,
validator: _model
.emailAddressTextControllerValidator
.asValidator(context),
),
],
),
),
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0, 18, 0, 0),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
0, 0, 0, 4),
child: Text(
'Password',
style: FlutterFlowTheme.of(context)
.bodyMedium
.override(
fontFamily: 'Inter',
letterSpacing: 0.0,
fontWeight: FontWeight.w600,
),
),
),
TextFormField(
controller: _model.passwordTextController,
focusNode: _model.passwordFocusNode,
autofocus: false,
autofillHints: [AutofillHints.password],
textInputAction: TextInputAction.done,
obscureText: !_model.passwordVisibility,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0x00000000),
width: 1.0,
),
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0x00000000),
width: 1.0,
),
borderRadius: BorderRadius.circular(8),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0x00000000),
width: 1.0,
),
borderRadius: BorderRadius.circular(8),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0x00000000),
width: 1.0,
),
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: FlutterFlowTheme.of(context)
.secondaryBackground,
suffixIcon: InkWell(
onTap: () => safeSetState(
() => _model.passwordVisibility =
!_model.passwordVisibility,
),
focusNode:
FocusNode(skipTraversal: true),
child: Icon(
_model.passwordVisibility
? Icons.visibility_outlined
: Icons.visibility_off_outlined,
color: FlutterFlowTheme.of(context)
.secondaryText,
size: 18,
),
),
),
style: FlutterFlowTheme.of(context)
.bodyMedium
.override(
fontFamily: 'Inter',
fontSize: 16,
letterSpacing: 0.0,
fontWeight: FontWeight.w500,
lineHeight: 1,
),
cursorColor:
FlutterFlowTheme.of(context).primary,
validator: _model
.passwordTextControllerValidator
.asValidator(context),
),
],
),
),
],
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(0, 24, 0, 0),
child: FFButtonWidget(
onPressed: () async {
logFirebaseEvent('SIGN_IN_PAGE_SIGN_IN_BTN_ON_TAP');
logFirebaseEvent('Button_haptic_feedback');
HapticFeedback.lightImpact();
logFirebaseEvent('Button_auth');
GoRouter.of(context).prepareAuthEvent();
final user = await authManager.signInWithEmail(
context,
_model.emailAddressTextController.text,
_model.passwordTextController.text,
);
if (user == null) {
return;
}
context.goNamedAuth('Dashboard', context.mounted);
},
text: 'Sign In',
options: FFButtonOptions(
width: double.infinity,
height: 50,
padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0),
iconPadding:
EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0),
color: FlutterFlowTheme.of(context).primary,
textStyle: FlutterFlowTheme.of(context)
.titleSmall
.override(
fontFamily: 'Inter',
letterSpacing: 0.0,
),
elevation: 0,
borderSide: BorderSide(
color: Colors.transparent,
width: 1,
),
borderRadius: BorderRadius.circular(25),
),
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(0, 12, 0, 0),
child: InkWell(
splashColor: Colors.transparent,
focusColor: Colors.transparent,
hoverColor: Colors.transparent,
highlightColor: Colors.transparent,
onTap: () async {
logFirebaseEvent(
'SIGN_IN_PAGE_Row_4ukbm94e_ON_TAP');
logFirebaseEvent('Row_navigate_to');
context.pushNamed('ForgotPassword');
},
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
0, 12, 0, 12),
child: Text(
'I don\'t remember my password',
style: FlutterFlowTheme.of(context)
.bodySmall
.override(
fontFamily: 'Inter',
letterSpacing: 0.0,
),
),
),
],
),
),
),
],
),
),
),
if (!(isWeb
? MediaQuery.viewInsetsOf(context).bottom > 0
: _isKeyboardVisible))
Padding(
padding: EdgeInsetsDirectional.fromSTEB(24, 0, 24, 48),
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: FlutterFlowTheme.of(context).secondaryBackground,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: FlutterFlowTheme.of(context).alternate,
width: 1,
),
),
child: Padding(
padding: EdgeInsetsDirectional.fromSTEB(24, 16, 24, 20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0, 0, 0, 12),
child: Text(
'Don\'t have an account yet?',
style: FlutterFlowTheme.of(context)
.labelLarge
.override(
fontFamily: 'Inter',
letterSpacing: 0.0,
),
),
),
FFButtonWidget(
onPressed: () async {
logFirebaseEvent(
'SIGN_IN_PAGE_CREATE_ACCOUNT_BTN_ON_TAP');
logFirebaseEvent('Button_navigate_to');
context.pushNamed('Onboarding_CreateAccount');
},
text: 'Create Account',
options: FFButtonOptions(
width: double.infinity,
height: 50,
padding:
EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0),
iconPadding:
EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0),
color: FlutterFlowTheme.of(context).accent1,
textStyle: FlutterFlowTheme.of(context)
.bodyMedium
.override(
fontFamily: 'Inter',
letterSpacing: 0.0,
),
elevation: 0,
borderSide: BorderSide(
color: FlutterFlowTheme.of(context).primary,
width: 2,
),
borderRadius: BorderRadius.circular(25),
),
),
],
),
),
),
),
],
),
),
),
);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment