โ›น๏ธ ๋ผ์ดํ”„/2021 ์—ฌ๋ฆ„๋ฐฉํ•™ ๋ชจ๊ฐ์ฝ”(๊ฐœ์ธ)

[์ฝ”๋…ํ•˜๊ตฌ๋งŒ 2] 3์ฃผ์ฐจ - Flutter : Personal Expense App

2021. 7. 22. 22:59

์ง€๋‚œ ์ฃผ์— ์ด์–ด Udemy ๊ฐ•์˜์—์„œ ๋งŒ๋“ค์–ด๋ณธ ๊ฐœ์ธ ๊ฐ€๊ณ„๋ถ€ ์–ดํ”Œ์„ ๋ฆฌ๋ทฐํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค. ์ง€๋‚œ ์ฃผ์—๋Š” ๋ฏธ์™„์„ฑ์ด์˜€๋Š”๋ฐ ๊ฐ•์˜๋ฅผ ๋…ํ•™ํ•˜๋ฉด์„œ ๋‹ค ์™„์„ฑํ–ˆ๋‹ค. ๋ญ”๊ฐ€ ์‚ด์ง ์–ด์„คํ”„์ง€๋งŒ ์–ด๋А์ •๋„ ํ˜ผ์ž์„œ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค. ์ฃผ๋ง์— ๋‚จ์€ 50%์˜ ๊ฐ•์˜๋ฅผ ๋‹ค ๋“ฃ๊ณ  ํ˜ผ์ž ๋งŒ๋“ค์–ด๋ณด๋ฉด์„œ ๋ณต์Šตํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค. ์•„๋ฌดํŠผ 3์ฃผ์ฐจ ๋ชจ๊ฐ์ฝ” ์Šคํƒ€ํŠธ!

 

์šฐ์„  ์ „์ฒด์ ์ธ UI๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

 

์†Œ๋น„ ํ•ญ๋ชฉ์ด ์—†๋Š” ๊ฒฝ์šฐ zZ์˜ ์ด๋ฏธ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๋ฉฐ ์ƒ๋‹จ์—๋Š” Chart๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” Switch์™€ ์ƒˆ๋กœ์šด ์†Œ๋น„ ํ•ญ๋ชฉ์„ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋Š” Button์ด AppBar์˜ ์šฐ์ธก๊ณผ Home์˜ ํ•˜๋‹จ์— ์กด์žฌํ•œ๋‹ค. Switch๋ฅผ Onํ•˜๋ฉด ์˜ค๋Š˜์„ ๊ธฐ์ค€์œผ๋กœ ์ง€๋‚œ 1์ฃผ์ผ ๊ฐ„์˜ ์†Œ๋น„๊ธˆ์•ก์ด ์ถœ๋ ฅ๋œ๋‹ค. ๊ธ€์„ ์ž‘์„ฑํ•˜๊ณ  ์žˆ๋Š” ์˜ค๋Š˜์€ ๋ชฉ์š”์ผ์ด๋ฏ€๋กœ Thursday.

 

์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด์ž. ๋‹ค ์ง€์šฐ๊ณ  main.dart์—์„œ Switch์™€ FloatingButton๊ณผ ๊ด€๋ จ๋œ Widget๋“ค๋งŒ ๋‚จ๊ฒจ๋†จ๋‹ค.

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    final isLandscape = MediaQuery.of(context).orientation == Orientation.landscape;
    final appBar = AppBar(
      title: Text('๊ฐ€๊ณ„๋ถ€'),
      actions: <Widget>[
        Builder(
          builder: (context) => IconButton(
            icon: Icon(Icons.add),
            onPressed: () => _startAddNewTransaction(context),
          ),
        ),
      ],
    );

    return Scaffold(
      appBar: appBar,
      body: SingleChildScrollView(
        child: Column(
          // mainAxisAlignment: MainAxisAlignment.spaceAround,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          // Column์ด๋ฏ€๋กœ main์€ y์ถ• (์œ„์—์„œ ์•„๋ž˜)
          children: <Widget>[
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text('Show Chart'),
                Switch(
                    value: _showChart,
                    onChanged: (val) {
                      setState(() {
                        _showChart = val;
                      });
                    }),
              ],
            ),
            _showChart
                ? Container(
                    height: (MediaQuery.of(context).size.height -
                            appBar.preferredSize.height -
                            MediaQuery.of(context).padding.top) *
                        0.3,
                    child: Chart(_recentTransactions),
                  )
                : Container(
                    height: (MediaQuery.of(context).size.height -
                            appBar.preferredSize.height -
                            MediaQuery.of(context).padding.top) *
                        0.7,
                    child:
                        TransactionList(_userTransactions, _deleteTransaction)),
          ],
        ),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
      floatingActionButton: Builder(
        builder: (context) => FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () => _startAddNewTransaction(context),
        ),
      ),
    );
  }
}

 

Switch๊ฐ€ ์‹คํ–‰๋˜๋ฉด(onChanged) setState๋ฅผ ์ด์šฉํ•˜์—ฌ _showChart์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•œ๋‹ค. ํ•˜๋‹จ์— ์‚ผํ•ญ์—ฐ์‚ฐ์ž ์ฝ”๋“œ๊ฐ€ ์žˆ๋Š”๋ฐ ์ด ๋ถ€๋ถ„์„ ๋ณด์ž. _showChart๊ฐ€ true์ธ ๊ฒฝ์šฐ Chart๋ฅผ ํ˜ธ์ถœํ•˜๋ฉฐ Chart๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ TransactionLIst๋ฅผ ํ˜ธ์ถœํ•˜๋ฉฐ ์†Œ๋น„ ๊ธฐ๋ก๋“ค์„ ๋ณด์—ฌ์ค€๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด Chart๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ChartBar๋Š” ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„๋˜์–ด ์žˆ์„๊นŒ?

import 'package:flutter/material.dart';

class ChartBar extends StatelessWidget {
  final String label;
  final double spendingAmount;
  final double spendingPctOfTotal;

  ChartBar(this.label, this.spendingAmount, this.spendingPctOfTotal);

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (ctx, constraints) {
      return Column(
        children: <Widget>[
          Container(
            height: constraints.maxHeight * 0.15,
            child: FittedBox(
              child: Text('${spendingAmount.toStringAsFixed(0)}์›'),
            ),
          ),
          SizedBox(
            height: constraints.maxHeight * 0.05,
          ),
          Container(
            height: constraints.maxHeight * 0.6,
            width: 10,
            child: Stack(
              children: <Widget>[
                Container(
                  decoration: BoxDecoration(
                    border: Border.all(color: Colors.grey, width: 1.0),
                    color: Color.fromRGBO(220, 220, 220, 1),
                    borderRadius: BorderRadius.circular(10),
                  ),
                ),
                FractionallySizedBox(
                  heightFactor: spendingPctOfTotal,
                  child: Container(
                    decoration: BoxDecoration(
                      color: Theme.of(context).primaryColor,
                      borderRadius: BorderRadius.circular(10),
                    ),
                  ),
                ),
              ],
            ),
          ),
          SizedBox(
            height: constraints.maxHeight * 0.05,
          ),
          Container(
            height: constraints.maxHeight * 0.15,
            child: FittedBox(
              child: Text(label),
            ),
          ),
        ],
      );
    });
  }
}

ChartBar์˜ ๊ฒฝ์šฐ LayoutBuilder๋กœ ๊ตฌํ˜„ํ•œ๋‹ค. LayoutBuilder๋Š” ๋ฐ˜์‘ํ˜• ๋ ˆ์ด์•„์›ƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค. ๋ถ€๋ชจ์˜ ๋ ˆ์ด์•„์›ƒ ํฌ๊ธฐ์— ๋”ฐ๋ผ ์ž์‹๋“ค์˜ ๋ ˆ์ด์•„์›ƒ์„ ์ •ํ•œ๋‹ค. builer์˜ ์ธ์ž๋กœ ctx, constraints๊ฐ€ ๋„˜์–ด๊ฐ€๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ constraints๊ฐ€ ๋ถ€๋ชจ์˜ ํฌ๊ธฐ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค. ์ดํ›„ Column๋“ค์„ ์‚ดํŽด๋ณด๋ฉด constriants.maxHeight๊ณผ ๊ฐ™์ด ๋ถ€๋ชจ์˜ ํฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์ด๋Ÿฐ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ๋‹ค์–‘ํ•œ ์‚ฌ์ด์ฆˆ์˜ ํ•ธ๋“œํฐ์—์„œ ๊ฐ™์€ ๋ชจ์Šต์˜ UI๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. ์ข€ ๋” ์ž์„ธํ•œ ์„ค๋ช…์„ ์œ„ํ•ด ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋“ค๊ณ ์™€๋ดค๋‹ค.

 

https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html

 

LayoutBuilder class - widgets library - Dart API

Builds a widget tree that can depend on the parent widget's size. Similar to the Builder widget except that the framework calls the builder function at layout time and provides the parent widget's constraints. This is useful when the parent constrains the

api.flutter.dev

 

์ค‘๊ฐ„์— Stack์ด ์ฒ˜์Œ ๋“ฑ์žฅํ•˜๋Š”๋ฐ Widget๋“ค์„ ๊ฒน์น˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ์–ด๋””์— ์‚ฌ์šฉํ–ˆ๋ƒ๋ฉด ์†Œ๋น„ ํ•ญ๋ชฉ์ด ์ถ”๊ฐ€๋  ๋•Œ ๊ฐ๊ฐ์˜ ChartBar์— ์ง€์ •ํ•œ ์ƒ‰์œผ๋กœ ChartBar๊ฐ€ ์ถ”๊ฐ€๋˜๋Š”๋ฐ ์ด ๋ถ€๋ถ„์ด ๋‘ ๊ฐœ์˜ Widget์ด ๊ฒน์ณ์ง„ ๋ถ€๋ถ„์ด๋‹ค. ๋ง๋กœ ์„ค๋ช…ํ•˜์ž๋ฉด ์–ด๋ ค์šฐ๋‹ˆ ์‚ฌ์ง„์„ ๋ณด์ž.

 

์ด๋Ÿฐ ์‹์œผ๋กœ Grey์˜ ChartBar์™€ Blue์˜ ChartBar๊ฐ€ ๊ฒน์น˜๊ฒŒ ๋˜๋Š”๋ฐ ์ด๋ฅผ ์œ„ํ•ด Stack๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

 

๋‹ค์Œ์€ new_transaction.dart. ํŒŒ์ผ๋ช… ๊ทธ๋Œ€๋กœ ์ƒˆ๋กœ์šด ํŠธ๋žœ์žญ์…˜์ด ์ถ”๊ฐ€๋  ๋•Œ์˜ Widget์ด๋‹ค. ์ง€๋‚œ ์ฃผ์™€ ๊ฑฐ์˜ ์œ ์‚ฌํ•˜์ง€๋งŒ ์ถ”๊ฐ€๋œ ๊ฒƒ์ด ์žˆ๋‹ค๋ฉด ๋‚ ์งœ ์„ ํƒ๊ณผ ์„ ํƒํ•œ ๋‚ ์งœ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค๋Š” ์ ์ด๋‹ค. ์ด ๋ถ€๋ถ„์€ DatePicket๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

 

 

์ฝ”๋“œ๊ฐ€ ์ข€ ๊ธธ๊ธดํ•œ๋ฐ ์ฒœ์ฒœํžˆ ์‚ดํŽด๋ณด์ž.

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

class NewTransaction extends StatefulWidget {
  final Function addTx;

  NewTransaction(this.addTx);

  @override
  State<NewTransaction> createState() => _NewTransactionState();
}

class _NewTransactionState extends State<NewTransaction> {
  final _titleController = TextEditingController();
  final _amountController = TextEditingController();
  DateTime _selectedDate = DateTime.now();

  void _submitData() {
    final enteredTitle = _titleController.text;
    final enteredAmount = double.parse(_amountController.text);

    if (enteredTitle.isEmpty || enteredAmount <= 0) {
      return;
    }
    widget.addTx(
      enteredTitle,
      enteredAmount,
      _selectedDate,
    );

    Navigator.of(context).pop();
  }

  void _presentDatePicket() {
    showDatePicker(
      context: context,
      initialDate: DateTime.now(),
      firstDate: DateTime(2000),
      lastDate: DateTime.now(),
    ).then(
      (pickedDate) {
        if (pickedDate == null) {
          return;
        }
        setState(
          () {
            _selectedDate = pickedDate;
          },
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Card(
        elevation: 3,
        child: Container(
          padding: EdgeInsets.only(top : 10, left : 10, right : 10, bottom : MediaQuery.of(context).viewInsets.bottom + 10),
          child: Container(
            padding: EdgeInsets.only(
              top: 10,
              left: 10,
              right: 10,
              bottom: MediaQuery.of(context).viewInsets.bottom + 10,
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.end,
              children: <Widget>[
                TextField(
                  decoration: InputDecoration(labelText: 'ํ’ˆ๋ชฉ๋ช…'),
                  // 1๋ฒˆ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ์•„๋ž˜ 1์ค„์ฒ˜๋Ÿผ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ
                  // onChanged: (val) => titleInput = val,
                  controller: _titleController,
                  onSubmitted: (_) => _submitData,
                ),
                TextField(
                  decoration: InputDecoration(labelText: '๊ฐ€๊ฒฉ'),
                  // 1๋ฒˆ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ์•„๋ž˜ 1์ค„์ฒ˜๋Ÿผ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ
                  // onChanged: (val) => amountInput = val,
                  controller: _amountController,
                  onSubmitted: (_) => _submitData,
                ),
                Row(
                  children: <Widget>[
                    Expanded(
                      child: Text(
                        _selectedDate == null
                            ? "Picked Date"
                            : '์„ ํƒ๋œ ๋‚ ์งœ : ${DateFormat.yMd().format(_selectedDate)}',
                      ),
                    ),
                    FlatButton(
                      textColor: Theme.of(context).primaryColor,
                      child: Text("๋‚ ์งœ ์„ ํƒ"),
                      onPressed: _presentDatePicket,
                    ),
                  ],
                ),
                RaisedButton(
                  child: Text("ํ•ญ๋ชฉ ์ถ”๊ฐ€"),
                  color: Colors.blueAccent,
                  textColor: Colors.white,
                  onPressed: _submitData,
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

_presentDataPicket์ด ๋‚ ์งœ ์„ ํƒ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค. ์šฐ์„  ํ•ด๋‹น ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ DatePicket๊ฐ€ ์‹คํ–‰๋˜๋ฉด์„œ ๋‹ฌ๋ ฅ์ด ๋„์–ด์ง€๋Š”๋ฐ ์ด ๋•Œ ์ดˆ๊ธฐ ๋‚ ์งœ๋ฅผ ๊ณ ๋ฅผ ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ถ€๋ถ„์ด ๋ฐ”๋กœ initialDate๊ฐ€ ๋œ๋‹ค. FristDate๋Š” ์„ ํƒ ๊ฐ€๋Šฅํ•œ ์ตœ์†Œ(?) ๋‚ ์งœ, lastDate๋Š” ์„ ํƒ ๊ฐ€๋Šฅํ•œ ์ตœ๋Œ€(?) ๋‚ ์งœ์ด๋‹ค. .then์„ ์ด์šฉํ•˜์—ฌ ์„ ํƒ๋œ ๋‚ ์งœ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ์˜ค๋Š˜ ๋‚ ์งœ๋ฅผ ๊ทธ๋Œ€๋กœ returnํ•˜๊ณ  ์„ ํƒ๋œ ๊ฒฝ์šฐ ํ•ด๋‹น ๋‚ ์งœ๋ฅผ returnํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ๋‚ ์งœ๋ฅผ ์„ ํƒํ•˜๊ณ  ํ•˜๋‹จ์˜ ํ•ญ๋ชฉ ์ถ”๊ฐ€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด _submitData๊ฐ€ ์‹คํ–‰๋˜๋ฉด์„œ transactionList์— ์ƒˆ๋กœ์šด ์†Œ๋น„ ํ•ญ๋ชฉ์ด ์ถ”๊ฐ€๋œ๋‹ค.

 

๋ญ”๊ฐ€ ๊ธ‰ํ•˜๊ฒŒ ๋งˆ๋ฌด๋ฆฌํ•œ ๋А๋‚Œ์ด ์žˆ์ง€๋งŒ ์‹ค์ œ๋กœ ๋‚˜ ํ˜ผ์ž์„œ ๋‹ค์‹œ ํ•œ ๋ฒˆ ์งœ๋ณด๋ฉด์„œ ๋‹ค์‹œ ํ•œ๋ฒˆ ์ •๋ฆฌํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค. ํ™•์‹คํžˆ Flutter๋ฅผ ์ฒ˜์Œ ์ ‘ํ–ˆ์„ ๋•Œ๋ณด๋‹ค๋Š” ๋งŽ์ด ๋Š˜์€ ๊ฒƒ ๊ฐ™์ง€๋งŒ ์•„์ง ๊ฐˆ ๊ธธ์ด ๋จผ ๊ฒƒ ๊ฐ™๋‹ค.

 

์ €์ž‘์žํ‘œ์‹œ ๋น„์˜๋ฆฌ ๋ณ€๊ฒฝ๊ธˆ์ง€ (์ƒˆ์ฐฝ์—ด๋ฆผ)

'โ›น๏ธ ๋ผ์ดํ”„ > 2021 ์—ฌ๋ฆ„๋ฐฉํ•™ ๋ชจ๊ฐ์ฝ”(๊ฐœ์ธ)' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[์ฝ”๋…ํ•˜๊ตฌ๋งŒ 2] 6์ฃผ์ฐจ - Flutter : Shop App 3  (0) 2021.08.10
[์ฝ”๋…ํ•˜๊ตฌ๋งŒ 2] 5์ฃผ์ฐจ - Flutter : Shop App 2  (0) 2021.08.10
[์ฝ”๋…ํ•˜๊ตฌ๋งŒ 2] 4์ฃผ์ฐจ - Flutter : Shop App 1  (0) 2021.07.28
[์ฝ”๋…ํ•˜๊ตฌ๋งŒ 2] 2์ฃผ์ฐจ - Flutter : Personal Expense App  (0) 2021.07.15
[์ฝ”๋…ํ•˜๊ตฌ๋งŒ 2] 1์ฃผ์ฐจ - Flutter : Quiz App  (0) 2021.07.07
'โ›น๏ธ ๋ผ์ดํ”„/2021 ์—ฌ๋ฆ„๋ฐฉํ•™ ๋ชจ๊ฐ์ฝ”(๊ฐœ์ธ)' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • [์ฝ”๋…ํ•˜๊ตฌ๋งŒ 2] 5์ฃผ์ฐจ - Flutter : Shop App 2
  • [์ฝ”๋…ํ•˜๊ตฌ๋งŒ 2] 4์ฃผ์ฐจ - Flutter : Shop App 1
  • [์ฝ”๋…ํ•˜๊ตฌ๋งŒ 2] 2์ฃผ์ฐจ - Flutter : Personal Expense App
  • [์ฝ”๋…ํ•˜๊ตฌ๋งŒ 2] 1์ฃผ์ฐจ - Flutter : Quiz App
kodo_o
kodo_o
iOS ๊ฟ€์žผ!
kodo_o
๐ŸŽ๐Ÿ
kodo_o
์ „์ฒด
์˜ค๋Š˜
์–ด์ œ
  • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (149)
    • ๐Ÿ”จ ํ”„๋กœ์ ํŠธ (0)
      • TP 1 (0)
      • WhiteHCCTV (0)
      • FootPrint (0)
    • ๐Ÿ’ป ๊ฐœ๋ฐœ (63)
      • iOS (30)
      • Android (6)
      • Kotlin (4)
      • Flutter (9)
      • Node.js (5)
      • Architecture (1)
      • ์˜ค๋Š˜์˜ ์‚ฝ์งˆ (7)
      • ์—๋Ÿฌ์™€์˜ ๋™์นจ (1)
    • โœ๏ธ ์•Œ๊ณ ๋ฆฌ์ฆ˜ (6)
      • Graph (6)
      • String (0)
      • Sort (0)
    • โœ๏ธ ์ฝ”ํ…Œ ์ค€๋น„ (44)
      • Math (1)
      • Implementation (3)
      • String (3)
      • Brute Force (5)
      • Back Tracking (7)
      • Greedy (0)
      • Dynamic Programming (13)
      • Binary Search (1)
      • DFS, BFS (5)
      • Shortest Path (2)
      • Two Pointer (4)
      • MST (0)
    • ๐Ÿ“š CS (6)
      • Operating System (6)
    • โ›น๏ธ ๋ผ์ดํ”„ (30)
      • 2020 ๊ฒจ์šธ๋ฐฉํ•™ ๋ชจ๊ฐ์ฝ”(๊ฐœ์ธ) (12)
      • 2021 ์—ฌ๋ฆ„๋ฐฉํ•™ ๋ชจ๊ฐ์ฝ”(๊ฐœ์ธ) (6)
      • ์ฝ”๋”ฉ ํ…Œ์ŠคํŠธ (1)
      • ํšŒ๊ณ  (10)

๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

  • ํ™ˆ
  • ๊นƒํ—ˆ๋ธŒ

์ธ๊ธฐ ๊ธ€

์ตœ๊ทผ ๊ธ€

์ตœ๊ทผ ๋Œ“๊ธ€

hELLO ยท Designed By ์ •์ƒ์šฐ.
kodo_o
[์ฝ”๋…ํ•˜๊ตฌ๋งŒ 2] 3์ฃผ์ฐจ - Flutter : Personal Expense App
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”

๋‹จ์ถ•ํ‚ค

๋‚ด ๋ธ”๋กœ๊ทธ

๋‚ด ๋ธ”๋กœ๊ทธ - ๊ด€๋ฆฌ์ž ํ™ˆ ์ „ํ™˜
Q
Q
์ƒˆ ๊ธ€ ์“ฐ๊ธฐ
W
W

๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๊ธ€

๊ธ€ ์ˆ˜์ • (๊ถŒํ•œ ์žˆ๋Š” ๊ฒฝ์šฐ)
E
E
๋Œ“๊ธ€ ์˜์—ญ์œผ๋กœ ์ด๋™
C
C

๋ชจ๋“  ์˜์—ญ

์ด ํŽ˜์ด์ง€์˜ URL ๋ณต์‚ฌ
S
S
๋งจ ์œ„๋กœ ์ด๋™
T
T
ํ‹ฐ์Šคํ† ๋ฆฌ ํ™ˆ ์ด๋™
H
H
๋‹จ์ถ•ํ‚ค ์•ˆ๋‚ด
Shift + /
โ‡ง + /

* ๋‹จ์ถ•ํ‚ค๋Š” ํ•œ๊ธ€/์˜๋ฌธ ๋Œ€์†Œ๋ฌธ์ž๋กœ ์ด์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ํ‹ฐ์Šคํ† ๋ฆฌ ๊ธฐ๋ณธ ๋„๋ฉ”์ธ์—์„œ๋งŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.