Django 백엔드의 flatter를 사용하여 애플리케이션 등록/로그인 #3
이 문장은 무엇에 관한 것입니까
앞의 두 문장에서 저는 DRF의 도움으로 Django로 API를 만들었습니다(본문 전에 읽으십시오: post #1, post #2.이 글은 이 API를 사용하여 실행되는 떨림 응용 프로그램을 소개합니다.사용자가 로그인하고 로그아웃할 수 있도록 프로그램을 만들 것입니다.
또한 this repo 에서 소스 코드를 볼 수 있습니다.
애플리케이션 데모:
선결 조건
본문을 읽으려면 바이브레이션 (참조: https://flutter.dev/docs/get-started/install 과 비주얼 스튜디오 코드와 바이브레이션 확장 (참조: https://flutter.dev/docs/development/tools/vs-code 과bloc (참조: https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc 을 설치해야 합니다.
시작합시다.
본문을 읽으려면 바이브레이션 (참조: https://flutter.dev/docs/get-started/install 과 비주얼 스튜디오 코드와 바이브레이션 확장 (참조: https://flutter.dev/docs/development/tools/vs-code 과bloc (참조: https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc 을 설치해야 합니다.
시작합시다.
다음은 코드의 다른 구성 요소를 분리하기 위해 일부 폴더를 만들 것입니다.bloc\u 로그인에서 다음 디렉터리 만들기/
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^3.2.0
meta: ^1.1.6
equatable: ^1.1.0
http: ^0.12.0+4
sqflite: ^1.3.0
path_provider: ^1.6.5
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
주의: 의존 항목 부분을 보여 드렸습니다. 의존 항목을 추가해야 하는 파일입니다.파일을 VScode에 저장하면 패키지를 가져오는 명령이 자동으로 실행되지만 참조하기 쉽도록
bloc_login/
디렉토리에서 다음 명령을 실행합니다.flutter pub get
user_database.dart
디렉터리에 파일bloc_directory/database
을 만들고 다음 코드를 추가합니다.import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
final userTable = 'userTable';
class DatabaseProvider {
static final DatabaseProvider dbProvider = DatabaseProvider();
Database _database;
Future <Database> get database async {
if (_database != null){
return _database;
}
_database = await createDatabase();
return _database;
}
createDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "User.db");
var database = await openDatabase(
path,
version: 1,
onCreate: initDB,
onUpgrade: onUpgrade,
);
return database;
}
void onUpgrade(
Database database,
int oldVersion,
int newVersion,
){
if (newVersion > oldVersion){}
}
void initDB(Database database, int version) async {
await database.execute(
"CREATE TABLE $userTable ("
"id INTEGER PRIMARY KEY, "
"username TEXT, "
"token TEXT "
")"
);
}
}
여기서 열린 데이터베이스를 DatabaseProvider 클래스의 database
변수로 되돌려줍니다.따라서 dbProvider
클래스를 초기화하고 get 데이터베이스 함수를 가지고 있으며, 이 함수는 열린 데이터베이스로 되돌아옵니다.서로 다른 버전 간에 약간의 변경이 필요하다면, 우리는
DatabaseProvider
함수를 사용할 수 있다.마지막 onUpgrade
변수는 데이터베이스를 저장하는 데 사용되는 테이블 이름입니다.userTable
을 만들고 다음 코드를 추가합니다.class User {
int id;
String username;
String token;
User(
{this.id,
this.username,
this.token});
factory User.fromDatabaseJson(Map<String, dynamic> data) => User(
id: data['id'],
username: data['username'],
token: data['token'],
);
Map<String, dynamic> toDatabaseJson() => {
"id": this.id,
"username": this.username,
"token": this.token
};
}
여기에서 우리는 공장 함수인fromDatabaseJson을 정의했다. 사용자를 JSON 대상으로 되돌려주고 toDatabaseJson은 전송된 JSON 대상을 데이터베이스에 저장할 수 있는 데이터베이스 대상으로 바꾸는 것을 책임진다.bloc_login/model/user_model.dart
을 만들고 다음 코드를 추가합니다.import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:bloc_login/model/api_model.dart';
final _base = "https://home-hub-app.herokuapp.com";
final _tokenEndpoint = "/api-token-auth/";
final _tokenURL = _base + _tokenEndpoint;
Future<Token> getToken(UserLogin userLogin) async {
print(_tokenURL);
final http.Response response = await http.post(
_tokenURL,
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(userLogin.toDatabaseJson()),
);
if (response.statusCode == 200) {
return Token.fromJson(json.decode(response.body));
} else {
print(json.decode(response.body).toString());
throw Exception(json.decode(response.body));
}
}
나는 위의 URL을 사용했다. 왜냐하면 나는 나의 응용 프로그램 home\uhub를 heroku라고 명명했기 때문이다. 너는 그것을 얻은 URL로 바꾸어야 한다.나머지 부분은 변하지 않을 수 있다.특정 사용자에게 영패를 가져오고 디테일이 정확하지 않은 상황에서 오류를 던지기 위해helper 함수만 있습니다.
bloc_login/api_connection/api_connection.dart
을 만들고 다음 코드를 추가합니다.class UserLogin {
String username;
String password;
UserLogin({this.username, this.password});
Map <String, dynamic> toDatabaseJson() => {
"username": this.username,
"password": this.password
};
}
class Token{
String token;
Token({this.token});
factory Token.fromJson(Map<String, dynamic> json) {
return Token(
token: json['token']
);
}
}
사용자로부터 사용자 이름과 비밀번호를 얻고 서버에 보낼 수 있도록 이 모델을 사용할 것입니다.Token 클래스에서 결과를 가져오고 서버에서 받은 JSON 객체에서 문자열을 제공합니다.bloc_login/model/api_model.dart
을 만들며 다음 코드를 추가합니다.import 'package:bloc_login/database/user_database.dart';
import 'package:bloc_login/model/user_model.dart';
class UserDao {
final dbProvider = DatabaseProvider.dbProvider;
Future<int> createUser(User user) async {
final db = await dbProvider.database;
var result = db.insert(userTable, user.toDatabaseJson());
return result;
}
Future<int> deleteUser(int id) async {
final db = await dbProvider.database;
var result = await db
.delete(userTable, where: "id = ?", whereArgs: [id]);
return result;
}
Future<bool> checkUser(int id) async {
final db = await dbProvider.database;
try {
List<Map> users = await db
.query(userTable, where: 'id = ?', whereArgs: [id]);
if (users.length > 0) {
return true;
} else {
return false;
}
} catch (error) {
return false;
}
}
}
이것은 기본적으로 사용자를 만들거나 삭제하고 사용자의 존재 여부를 검색하는 방법을 제공합니다.우리 프로그램은 최대 한 사용자에게만 로그인 기능을 제공하기 때문에, id가 0인 사용자를 강제로 만들고 있습니다.따라서 우리는 항상 데이터베이스에 있는 첫 번째 항목을 검사함으로써 사용자가 존재하는지 확인할 수 있다.가장 좋은 것은 우리가 입력한 비밀번호가 아니라 사용자 이름과 영패를 저장하는 것이다.암호는 서버에 요청을 보내는 데 사용될 뿐, 우리는 지금까지 그 어느 곳보다 저장된 적이 없다.여기에는 영패를 항상 저장하고 사용할 수 있는 대체 방법이 있습니다. 그러나 다른 기능을 포함하기 위해 프로그램을 확장하고 싶기 때문에 SQL을 사용하고 싶습니다.bloc_login/dao/user_dao.dart
디렉터리에 새로운bloc를 만들고 이를authentication_bloc라고 명명합니다.확장자는 자동으로
bloc_login.
폴더를 만들고 그 중에서 상태, 이벤트, authentication_bloc에 대응하는 세 개의 파일을 만듭니다.수정bloc_login/bloc
및 다음 코드 추가:part of 'authentication_bloc.dart';
abstract class AuthenticationState extends Equatable {
@override
List<Object> get props => [];
}
class AuthenticationUnintialized extends AuthenticationState {}
class AuthenticationAuthenticated extends AuthenticationState {}
class AuthenticationUnauthenticated extends AuthenticationState {}
class AuthenticationLoading extends AuthenticationState {}
여기서 우리는 네 가지 초기화되지 않은 인증 상태를 정의했다. 이것은 응용 프로그램이 데이터베이스에 사용자가 존재하는지 확인하기 위해 기다리는 상태에 대응하고, 불러오는 상태는 우리가 응용 프로그램이 영패를 저장하거나 삭제할 때 기다리는 상태이다.유효성 검사 상태는 로그인한 사용자에 해당하고 유효성 검사/로그아웃하지 않은 사용자에 해당합니다.bloc_login/bloc/authentication_state.dart
사용자를 수정하고 다음 코드를 추가합니다.part of 'authentication_bloc.dart';
abstract class AuthenticationEvent extends Equatable {
const AuthenticationEvent();
@override
List<Object> get props => [];
}
class AppStarted extends AuthenticationEvent {}
class LoggedIn extends AuthenticationEvent {
final User user;
const LoggedIn({@required this.user});
@override
List<Object> get props => [user];
@override
String toString() => 'LoggedIn { user: $user.username.toString() }';
}
class LoggedOut extends AuthenticationEvent {}
이것은 발생할 수 있는 세 가지 이벤트에 대응합니다. AppStarted는 블록에 사용자가 존재하는지 확인하는 것을 알려 줍니다. Loggedin은 사용자가 성공적으로 로그인했음을 표시하는 이벤트이고 Logged Out은 사용자가 로그아웃했음을 알려 줍니다.bloc_login/bloc/authentication_event.dart
하며 다음과 같은 내용을 추가해야 한다.import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';
import 'package:bloc_login/repository/user_repository.dart';
import 'package:bloc_login/model/user_model.dart';
part 'authentication_event.dart';
part 'authentication_state.dart';
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
final UserRepository userRepository;
AuthenticationBloc({@required this.userRepository})
: assert(UserRepository != null);
@override
AuthenticationState get initialState => AuthenticationUnintialized();
@override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
if (event is AppStarted) {
final bool hasToken = await userRepository.hasToken();
if (hasToken) {
yield AuthenticationAuthenticated();
} else {
yield AuthenticationUnauthenticated();
}
}
if (event is LoggedIn) {
yield AuthenticationLoading();
await userRepository.persistToken(
user: event.user
);
yield AuthenticationAuthenticated();
}
if (event is LoggedOut) {
yield AuthenticationLoading();
await userRepository.delteToken(id: 0);
yield AuthenticationUnauthenticated();
}
}
}
여기서 우리는 다음과 같은 일을 했다.- 인증할 초기화 상태가 초기화되지 않았습니다.
- 데이터베이스에 존재하는 사용자에 대응하는 AuthenticationAuthenticated 상태가 생성됩니다.이것은 우리가 램에서 프로그램을 제거해도 사용자가 존재하기 때문에 상태의 지속성에 도움이 될 것이다.
- 이벤트가 Loggedin인 경우 정의된persistToken 함수를 호출하여 사용자를 데이터베이스에 저장합니다.
- 이벤트가 LoggedOut이면 데이터베이스에서 사용자를 삭제합니다.
bloc_login/bloc/authentication_bloc.dart
및 다음 코드를 만듭니다.import 'package:flutter/material.dart';
class SplashPage extends StatelessWidget {
@override
Widget build (BuildContext context) {
return Scaffold(
body: Center(
child: Text('Splash Screen'),
),
);
}
}
또한 이 코드는 파일 bloc_login/splash_page.dart
에서 안내되어 액세스를 용이하게 합니다.export 'splash_page.dart';
bloc_login/splash/splash.dart
을 만들고 다음 코드를 추가합니다.import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:bloc_login/bloc/authentication_bloc.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home | Home Hub'),
),
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(padding: EdgeInsets.only(left: 30.0),
child: Text(
'Welcome',
style: TextStyle(
fontSize: 24.0,
),
),),
Padding(
padding: EdgeInsets.fromLTRB(34.0, 20.0, 0.0, 0.0),
child: Container(
width: MediaQuery.of(context).size.width * 0.85,
height: MediaQuery.of(context).size.width * 0.16,
child: RaisedButton(
child: Text(
'Logout',
style: TextStyle(
fontSize: 24,
),
),
onPressed: () {
BlocProvider.of<AuthenticationBloc>(context)
.add(LoggedOut());
},
shape: StadiumBorder(
side: BorderSide(
color: Colors.black,
width: 2,
),
),
),
),
),
],
),
),
);
}
}
여기에서 사용자를 환영하고 로그아웃하는 단추를 보여 줍니다. 이 단추를 누르면 로그아웃 이벤트가 발생했음을 알려 줍니다.bloc_login/home/home_page.dart
디렉터리의 확장자를 사용하여login이라는 다른 블록을 만들고, main.dart
에 다음 코드를 추가합니다part of 'login_bloc.dart';
abstract class LoginState extends Equatable {
const LoginState();
@override
List<Object> get props => [];
}
class LoginInitial extends LoginState {}
class LoginLoading extends LoginState {}
class LoginFaliure extends LoginState {
final String error;
const LoginFaliure({@required this.error});
@override
List<Object> get props => [error];
@override
String toString() => ' LoginFaliure { error: $error }';
}
여기에서 Login Initial 상태는 로그인 폼의 초기 상태이고 Login Loading은 폼 검증 인증서의 상태이며 Login Failure는 로그인 시도가 실패했음을 나타냅니다.bloc_login/login
part of 'login_bloc.dart';
abstract class LoginEvent extends Equatable {
const LoginEvent();
}
class LoginButtonPressed extends LoginEvent {
final String username;
final String password;
const LoginButtonPressed({
@required this.username,
@required this.password
});
@override
List<Object> get props => [username, password];
@override
String toString() => 'LoginButtonPressed { username: $username, password: $password }';
}
또한 로그인 이름을 추가하여 이벤트를 bloc_login/login/bloc/login_states.dart
의 상태로 매핑할 수 있습니다.이 파일에 다음 코드를 추가합니다.import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:bloc_login/bloc/authentication_bloc.dart';
import 'package:bloc_login/repository/user_repository.dart';
import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';
part 'login_event.dart';
part 'login_state.dart';
class LoginBloc extends Bloc<LoginEvent, LoginState> {
final UserRepository userRepository;
final AuthenticationBloc authenticationBloc;
LoginBloc({
@required this.userRepository,
@required this.authenticationBloc,
}) : assert(userRepository != null),
assert(authenticationBloc != null);
@override
LoginState get initialState => LoginInitial();
@override
Stream<LoginState> mapEventToState(
LoginEvent event,
) async* {
if (event is LoginButtonPressed) {
yield LoginInitial();
try {
final user = await userRepository.authenticate(
username: event.username,
password: event.password,
);
authenticationBloc.add(LoggedIn(user: user));
yield LoginInitial();
} catch (error) {
yield LoginFaliure(error: error.toString());
}
}
}
}
bloc_login/login/login_event.dart file and add the following code:
을 만들고 다음 코드를 추가합니다.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:bloc_login/login/bloc/login_bloc.dart';
class LoginForm extends StatefulWidget {
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
_onLoginButtonPressed() {
BlocProvider.of<LoginBloc>(context).add(LoginButtonPressed(
username: _usernameController.text,
password: _passwordController.text,
));
}
return BlocListener<LoginBloc, LoginState>(
listener: (context, state) {
if (state is LoginFaliure) {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('${state.error}'),
backgroundColor: Colors.red,
));
}
},
child: BlocBuilder<LoginBloc, LoginState>(
builder: (context, state) {
return Container(
child: Form(
child: Padding(
padding: EdgeInsets.all(40.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: 'username', icon: Icon(Icons.person)),
controller: _usernameController,
),
TextFormField(
decoration: InputDecoration(
labelText: 'password', icon: Icon(Icons.security)),
controller: _passwordController,
obscureText: true,
),
Container(
width: MediaQuery.of(context).size.width * 0.85,
height: MediaQuery.of(context).size.width * 0.22,
child: Padding(
padding: EdgeInsets.only(top: 30.0),
child: RaisedButton(
onPressed: state is! LoginLoading
? _onLoginButtonPressed
: null,
child: Text(
'Login',
style: TextStyle(
fontSize: 24.0,
),
),
shape: StadiumBorder(
side: BorderSide(
color: Colors.black,
width: 2,
),
),
),
),
),
Container(
child: state is LoginLoading
? CircularProgressIndicator()
: null,
),
],
),
),
),
);
},
),
);
}
}
또한 파일 bloc_login/login/login_bloc.dart
을 만들고 다음 코드를 추가합니다.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:bloc_login/repository/user_repository.dart';
import 'package:bloc_login/bloc/authentication_bloc.dart';
import 'package:bloc_login/login/bloc/login_bloc.dart';
import 'package:bloc_login/login/login_form.dart';
class LoginPage extends StatelessWidget {
final UserRepository userRepository;
LoginPage({Key key, @required this.userRepository})
: assert(userRepository != null),
super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login | Home Hub'),
),
body: BlocProvider(
create: (context) {
return LoginBloc(
authenticationBloc: BlocProvider.of<AuthenticationBloc>(context),
userRepository: userRepository,
);
},
child: LoginForm(),
),
);
}
}
우리는 모두 준비가 되었다. 지금 해야 할 일은 bloc_login/login/login_form.dart
파일을 만들고 로드 표시기를 만드는 것이다.먼저 로드 표시기를 만들고 bloc_login/login/login_page.dart
다음 코드를 추가합니다.
import 'package:flutter/material.dart';
class LoadingIndicator extends StatelessWidget {
@override
Widget build(BuildContext context) => Center(
child: CircularProgressIndicator(),
);
}
또한 다음과 같은 편리한 액세스를 위해 bloc_login/main.dart
출구로 이동합니다.
export './loading_indicator.dart';
마지막으로 bloc_login/common/loading_indicator.dart
에 다음 코드를 추가합니다
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:bloc_login/repository/user_repository.dart';
import 'package:bloc_login/bloc/authentication_bloc.dart';
import 'package:bloc_login/splash/splash.dart';
import 'package:bloc_login/login/login_page.dart';
import 'package:bloc_login/home/home.dart';
import 'package:bloc_login/common/common.dart';
class SimpleBlocDelegate extends BlocDelegate {
@override
void onEvent(Bloc bloc, Object event) {
super.onEvent(bloc, event);
print(event);
}
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print (transition);
}
@override
void onError(Bloc bloc, Object error, StackTrace stacktrace) {
super.onError(bloc, error, stacktrace);
}
}
void main() {
BlocSupervisor.delegate = SimpleBlocDelegate();
final userRepository = UserRepository();
runApp(
BlocProvider<AuthenticationBloc>(
create: (context) {
return AuthenticationBloc(
userRepository: userRepository
)..add(AppStarted());
},
child: App(userRepository: userRepository),
)
);
}
class App extends StatelessWidget {
final UserRepository userRepository;
App({Key key, @required this.userRepository}) : super(key: key);
@override
Widget build (BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.red,
brightness: Brightness.dark,
),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is AuthenticationUnintialized) {
return SplashPage();
}
if (state is AuthenticationAuthenticated) {
return HomePage();
}
if (state is AuthenticationUnauthenticated) {
return LoginPage(userRepository: userRepository,);
}
if (state is AuthenticationLoading) {
return LoadingIndicator();
}
},
),
);
}
}
디버깅을 위해 BlocDelegate를 추가하고 함수를 다시 썼습니다.나는 이것이 좀 긴 게시물이라는 것을 알고 있지만, 나는 그것이 좋은 것일 것이라고 생각한다. 전체 응용 프로그램을 포함하여 여기에 참고로 삼을 것이다.
이것은 이 시리즈의 마지막 문장이 될 것이다.또한 here 에서 소스 코드를 볼 수 있습니다.
이전 게시물 링크:
this repo
Flutter signup/login application with Django backend #1
저희가 제공하는 각종 개발 서비스를 얻기 위해 수시로 전화Flutter signup/login application with Django backend #2를 주십시오.
Reference
이 문제에 관하여(Django 백엔드의 flatter를 사용하여 애플리케이션 등록/로그인 #3), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/amartyadev/flutter-signup-login-application-with-django-backend-3-oo2텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)