내 첫 모바일 앱 만들기

Flutter와 Firebase를 사용하여 최초의 모바일 애플리케이션을 개발하고 있습니다. 나는 정기적으로 체육관에 가고 파워 리프팅에 관심이 있기 때문에 운동을 추적하고 시간이 지남에 따라 어떻게 향상되었는지 확인할 수 있는 운동 추적기 앱을 만들기로 결정했습니다.

앱이 어떤 모습일지 확신할 수 없었기 때문에 Flutter에서 UI를 구축하는 것부터 시작했습니다. 그래서 https://fireship.io:에 대한 자습서의 도움을 받아 초기 페이지를 만들기 시작했습니다.
  • 운동
  • 운동
  • 네트워크
  • 연혁
  • 프로필
  • 로그인

  • 로그인 페이지부터 시작하겠습니다.



    현재로서는 특별한 것이 없습니다. 게스트로 로그인하거나 Google을 사용하여 로그인할 수 있습니다. 이메일과 비밀번호(Google과 연결되지 않음)를 사용하여 로그인하는 기능을 추가할 계획입니다.

    import 'package:flutter/material.dart';
    import 'package:font_awesome_flutter/font_awesome_flutter.dart';
    import 'package:barbellplus/services/auth.dart';
    
    class LoginScreen extends StatelessWidget {
      const LoginScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Container(
            decoration: const BoxDecoration(
              image: DecorationImage(
                image: AssetImage('assets/images/login-background.jpg'),
                fit: BoxFit.cover,
              ),
            ),
            padding: const EdgeInsets.all(30),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Icon(
                  FontAwesomeIcons.dumbbell,
                  size: 150,
                  color: Color.fromARGB(255, 209, 5, 5),
                ),
                const Text(
                  'Barbell Plus',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    color: Color.fromARGB(255, 209, 5, 5),
                    fontSize: 40,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 120),
                Flexible(
                  child: LoginButton(
                    icon: FontAwesomeIcons.userNinja,
                    text: 'Continue as Guest',
                    loginMethod: AuthService().anonLogin,
                    color: Colors.deepPurple,
                  ),
                ),
                LoginButton(
                  text: 'Sign in with Google',
                  icon: FontAwesomeIcons.google,
                  color: Colors.blue,
                  loginMethod: AuthService().googleLogin,
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class LoginButton extends StatelessWidget {
      final Color color;
      final IconData icon;
      final String text;
      final Function loginMethod;
    
      const LoginButton(
          {super.key,
          required this.text,
          required this.icon,
          required this.color,
          required this.loginMethod});
    
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: const EdgeInsets.only(bottom: 10),
          child: ElevatedButton.icon(
            icon: Icon(
              icon,
              color: Colors.white,
              size: 20,
            ),
            style: TextButton.styleFrom(
              padding: const EdgeInsets.all(24),
              backgroundColor: color,
            ),
            onPressed: () => loginMethod(),
            label: Text(text, textAlign: TextAlign.center),
          ),
        );
      }
    }
    
    


    나는 이전에 flutter나 dart를 사용한 적이 없지만 이전에 react.js를 사용하여 일부 웹 애플리케이션을 만들었습니다. Flutter에서 작업이 수행되는 방식의 차이를 보는 것은 매우 흥미로웠습니다. 예를 들어 화면의 특정 지점에 위젯을 배치하는 것과 같이 일부 위젯에는 원하는 작업을 수행하기 위해 특정 작업이 필요하다는 사실이 때때로 짜증납니다.

    이제 나머지 페이지로 이동합니다.



    이 페이지는 응용 프로그램에서 사용할 수 있는 모든 연습 목록입니다. 원래는 UI를 제대로 만들기 위해 샘플 데이터를 사용했습니다.
    데이터베이스와 통합하기 시작하면서 실제 데이터 세트가 필요했습니다. 내 요구 사항에 맞는 것을 찾을 수 없습니다. 그래서 당분간은 필요한 데이터를 웹 스크랩하기로 결정했습니다. 이와 관련된 법적인 문제는 잘 모르겠지만 이 앱을 사용할 사람은 소수에 불과합니다. 위험을 감수해도 괜찮을 것 같아요. 그러나 만족스러운 데이터 세트를 찾으면 대신 사용할 것입니다.

    import 'package:barbellplus/exercises/exercise_dialog.dart';
    import 'package:barbellplus/services/firestore.dart';
    import 'package:barbellplus/services/models.dart';
    import 'package:flutter/material.dart';
    
    class ExerciseList extends StatelessWidget {
      const ExerciseList({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Expanded(
          child: DecoratedBox(
            decoration: const BoxDecoration(
              color: Colors.white,
            ),
            child: Padding(
              padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 30),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Expanded(
                    child: FutureBuilder<List<Exercise>>(
                      future: FirestoreService().getExercises(),
                      builder: (context, snapshot) {
                        if (snapshot.hasError) {
                          return const Text('Something went wrong');
                        }
    
                        if (snapshot.connectionState == ConnectionState.waiting) {
                          return const Center(
                            child: CircularProgressIndicator(
                                color: Color.fromARGB(255, 209, 5, 5)),
                          );
                        }
    
                        return ListView.builder(
                          itemCount: snapshot.data!.length,
                          itemBuilder: (context, index) => ExerciseItem(
                            exercise: snapshot.data![index],
                          ),
                        );
                      },
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class ExerciseItem extends StatelessWidget {
      final Exercise exercise;
    
      // ignore: prefer_const_constructors_in_immutables
      ExerciseItem({super.key, required this.exercise});
    
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => ExerciseDialog(exercise: exercise),
              ),
            );
          },
          child: Container(
              margin: const EdgeInsets.symmetric(vertical: 5),
              height: 65,
              width: double.infinity,
              decoration: BoxDecoration(
                color: Colors.black12,
                borderRadius: BorderRadius.circular(10),
              ),
              child: Row(
                children: [
                  const SizedBox(width: 10),
                  Stack(
                    children: [
                      Container(
                        height: 50,
                        width: 50,
                        decoration: BoxDecoration(
                          color: Colors.black12,
                          borderRadius: BorderRadius.circular(10),
                        ),
                      ),
                      Container(
                        height: 50,
                        width: 50,
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(10),
                          image: const DecorationImage(
                            image:
                                AssetImage('assets/images/image-unavailable.png'),
                            fit: BoxFit.cover,
                          ),
                        ),
                      ),
                      Container(
                        height: 50,
                        width: 50,
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(10),
                          image: DecorationImage(
                            image: NetworkImage(exercise.image),
                            fit: BoxFit.cover,
                          ),
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(width: 20),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const SizedBox(height: 10),
                        Text(exercise.name,
                            overflow: TextOverflow.ellipsis,
                            textAlign: TextAlign.left,
                            style: const TextStyle(
                              color: Colors.black,
                              fontWeight: FontWeight.bold,
                              fontSize: 14,
                            )),
                        const SizedBox(height: 5),
                        Text(
                            '${exercise.muscle}${(exercise.equipment)}${exercise.difficulty}',
                            overflow: TextOverflow.ellipsis,
                            textAlign: TextAlign.left,
                            style: const TextStyle(
                              color: Colors.red,
                              fontWeight: FontWeight.bold,
                              fontStyle: FontStyle.italic,
                              fontSize: 12,
                            )),
                      ],
                    ),
                  ),
                  const SizedBox(width: 10),
                ],
              )),
        );
      }
    }
    
    


    프로필 페이지로 이동: 이 페이지는 아직 완료되지 않았습니다. 약간의 개인 정보, 개인 진행 상황 이미지의 캐러셀 및 아직 결정되지 않은 다른 기능을 제공하는 다른 버튼이 있습니다.



    나머지 페이지, 운동 및 네트워크는 아직 완료되지 않았습니다. 운동 페이지는 운동 프로그램을 선택하고 운동 데이터를 입력하는 곳입니다. 현재 클라우드 Firestore와 통합하는 과정에 있지만 약간의 어려움이 있습니다.
    네트워크 페이지는 친구와 운동 진행 상황을 공유할 수 있는 앱의 사회적 측면이어야 하는지 잘 모르겠습니다. 어떻게 작동하고 싶은지 잘 모르겠습니다.

    좋은 웹페이지 즐겨찾기