ساخت اپلیکیشن ماشین حساب با فلاتر — از صفر تا صد

۶۰۰ بازدید
آخرین به‌روزرسانی: ۱۲ مهر ۱۴۰۲
زمان مطالعه: ۱۲ دقیقه
ساخت اپلیکیشن ماشین حساب با فلاتر — از صفر تا صد

در این مقاله به بررسی شیوه طراحی و ساخت یک اپلیکیشن ماشین حساب ساده با استفاده از Dart و Flutter می‌پردازیم. زبان برنامه‌نویسی دارت و SDK فلاتر ویژگی‌هایی عالی در اختیار توسعه‌دهندگان قرار می‌دهند و یک کیت ابزار کامل برای امتحان کردن سریع ایده‌های جدید و ساخت اپلیکیشن‌های با عملکرد بالا محسوب می‌شوند. با ما تا انتهای این راهنمای ساخت اپلیکیشن ماشین حساب با فلاتر همراه باشید.

اپلیکیشن ساده ماشین حساب ما شامل یک رابط کاربری ساده، چند عملیات و تابع ساده و یک خروجی است که نتیجه را نمایش می‌دهد. بنابراین یک اپلیکیشن آغازین یا قالب عالی برای طراحی و ساخت هر گونه اپلیکیشن دیگر محسوب می‌شود.

کد منبع این پروژه را می‌توانید در این ریپوی گیت‌هاب (+) ملاحظه کنید.

شروع توسعه اپلیکیشن ماشین حساب با فلاتر

پروژه این اپلیکیشن با استفاده از دستور flutter create و بدون نیاز به هیچ گونه پکیج یا وابستگی اضافی ایجاد می‌شود، زیرا همه قابلیت‌هایی که می‌خواهیم با استفاده از دارت و فلاتر خالی به روشی ساده و پایدار قابل پیاده‌سازی هستند.

بهره‌گیری از قابلیت‌های داخلی یک زبان و SDK باعث می‌شود که معماری یک اپلیکیشن تا حد امکان منعطف و کارآمد بماند و در نتیجه عملکرد پروژه افزایش یابد، فایل‌های کامپایل شده اپلیکیشن سریع‌تر باشند و بدهی فنی کمتری ایجاد شود.

برای ساخت این پروژه یا ایجاد هر نوع پروژه مشابه تنها دو مورد زیر لازم هستند:

  • Dart
  • Flutter SDK

نقطه ورود اپلیکیشن

نقطه ورود اپلیکیشن برای این پروژه استاندارد فلاتر به صورت lib/main.dart است:

1import 'package:flutter/material.dart';
2import 'package:flutter/services.dart';
3import 'package:flutter_calculator_demo/calculator.dart';
4
5void main() async {
6  await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
7  runApp(CalculatorApp());
8}
9
10class CalculatorApp extends StatelessWidget {
11
12  @override
13  Widget build(BuildContext context) {
14
15    return MaterialApp(
16      theme: ThemeData(primarySwatch: Colors.teal),
17      home: Calculator(),
18    );
19  }
20}

تابع main صفحه دستگاه را در حالت عمودی قفل می‌کند و سپس کلاس اصلی اپلیکیشن یعنی CalculatorApp را اجرا می‌کند که به نوبه خود یک MaterialApp می‌سازد که صفحه اصلی‌اش روی یک وهله از Calculator تنظیم شده است. در بخش بعدی به بررسی این کلاس می‌پردازیم.

کلاس Calculator

UX اصلی ماشین حساب در lib/calculator.dart تعریف شده است:

1import 'package:flutter/material.dart';
2import 'package:flutter_calculator_demo/display.dart';
3import 'package:flutter_calculator_demo/key-controller.dart';
4import 'package:flutter_calculator_demo/key-pad.dart';
5import 'package:flutter_calculator_demo/processor.dart';
6
7class Calculator extends StatefulWidget {
8
9  Calculator({ Key key }) : super(key: key);
10
11  @override
12  _CalculatorState createState() => _CalculatorState();
13}
14
15class _CalculatorState extends State<Calculator> {
16
17  String _output;
18
19  @override
20  void initState() {
21    
22    KeyController.listen((event) => Processor.process(event));
23    Processor.listen((data) => setState(() { _output = data; }));
24    Processor.refresh();
25    super.initState();
26  }
27
28  @override
29  void dispose() {
30
31    KeyController.dispose();
32    Processor.dispose();
33    super.dispose();
34  }
35
36  @override
37  Widget build(BuildContext context) {
38
39    Size screen = MediaQuery.of(context).size;
40
41    double buttonSize = screen.width / 4;
42    double displayHeight = screen.height - (buttonSize * 5) - (buttonSize);
43  
44    return Scaffold(
45      backgroundColor: Color.fromARGB(196, 32, 64, 96),
46      body: Column(
47        
48        mainAxisAlignment: MainAxisAlignment.center,
49        children: <Widget>[
50
51          Display(value: _output, height: displayHeight),
52          KeyPad()
53        ]
54      ),
55    );
56  }
57}

کلاس Calculator یک «ویجت حالت دار» (StatefulWidget) است که برای UX کلی اپلیکیشن ساخته شده است و پردازش کلیدها را به کلاس Processor ارسال کرده و داده‌ها را برای نمایش دریافت می‌کند. این کار به وسیله بهره‌گیری از استریم‌ها صورت می‌پذیرد که به جای تلاش برای پیاده‌سازی برخی نسخه‌های مدیریت حالت سنگین، پیام‌هایی را به صورت مستقیم بین کامپوننت‌ها رد و بدل می‌کند.

یک فراخوانی منفرد به setState وجود دارد که خروجی نمایشی را به‌روزرسانی می‌کند و هر چیز دیگر به وسیله ارسال استریم داده‌های رویداد ناهمگام بین کلاس‌های KeyPad ،KeyController ،Calculator و Processor اتفاق می‌افتد. این وضعیت تضمین می‌کند که گردش مورد نظر اطلاعات وجود دارد و به جلوگیری از شرایط رقابت و دیگر مشکلاتی که دیباگ کردنشان دشوار است کمک می‌کند.

در متد build، اندازه دکمه با تقسیم کردن عرض صفحه بر تعداد دکمه‌ها و ارتفاع Display به وسیله کم کردن ارتفاع ستون پنج دکمه‌ای (ارتفاع صفحه‌کلید عددی) از ارتفاع صفحه دستگاه به دست می‌آید.

ویجت Display

فایل بعدی که مورد بررسی قرار می‌دهیم نمایشگر ماشین حساب در فایل lib/display.dart است:

1import 'package:flutter/material.dart';
2
3class Display extends StatelessWidget {
4
5	Display({ Key key, this.value, this.height }) : super(key: key);
6
7	final String value;
8	final double height;
9
10	String get _output => value.toString();
11	double get _margin => (height / 10);
12
13	final LinearGradient _gradient = const LinearGradient(colors: [ Colors.black26, Colors.black45 ]);
14
15	@override
16	Widget build(BuildContext context) {
17		
18		TextStyle style = Theme.of(context).textTheme.display2
19			.copyWith(color: Colors.white, fontWeight: FontWeight.w200);
20
21		return Container(
22			padding: EdgeInsets.only(top: _margin, bottom: _margin),
23			constraints: BoxConstraints.expand(height: height),
24			child: Container(
25				padding: EdgeInsets.fromLTRB(32, 32, 32, 32),
26				constraints: BoxConstraints.expand(height: height - (_margin)),
27				decoration: BoxDecoration(gradient: _gradient),
28				child: Text(_output, style: style, textAlign: TextAlign.right, )
29			)
30		);
31	}
32}

کلاس Display یک پیاده‌سازی مقدماتی از «ویجت بی‌حالت» (StatelessWidget) یا یک value و height است که در سازنده تعیین شده‌اند. پس‌زمینه روی یک گرادیان خطی تنظیم شده و value به صورت متنی در گوشه راست-بالای نمایشگر نشان داده می‌شود.

ویجت KeyPad

در این بخش ویجت KeyPad ماشین حساب را که در فایل lib/key-pad.dart قرار دارد بررسی می‌کنیم:

1import 'package:flutter/widgets.dart';
2import 'package:flutter_calculator_demo/calculator-key.dart';
3
4class KeyPad extends StatelessWidget {
5
6  @override
7  Widget build(BuildContext context) {
8  
9    return Column(
10
11      children: [
12        Row(
13          children: <Widget>[
14            CalculatorKey(symbol: Keys.clear),
15            CalculatorKey(symbol: Keys.sign),
16            CalculatorKey(symbol: Keys.percent),
17            CalculatorKey(symbol: Keys.divide),
18          ]
19        ),
20        Row(
21          children: <Widget>[
22            CalculatorKey(symbol: Keys.seven),
23            CalculatorKey(symbol: Keys.eight),
24            CalculatorKey(symbol: Keys.nine),
25            CalculatorKey(symbol: Keys.multiply),
26          ]
27        ),
28        Row(
29          children: <Widget>[
30            CalculatorKey(symbol: Keys.four),
31            CalculatorKey(symbol: Keys.five),
32            CalculatorKey(symbol: Keys.six),
33            CalculatorKey(symbol: Keys.subtract),
34          ]
35        ),
36        Row(
37          children: <Widget>[
38            CalculatorKey(symbol: Keys.one),
39            CalculatorKey(symbol: Keys.two),
40            CalculatorKey(symbol: Keys.three),
41            CalculatorKey(symbol: Keys.add),
42          ]
43        ),
44        Row(
45          children: <Widget>[
46            CalculatorKey(symbol: Keys.zero),
47            CalculatorKey(symbol: Keys.decimal),
48            CalculatorKey(symbol: Keys.equals),
49          ]
50        )
51      ]
52    );
53  }
54}

ویجت KeyPad یک ستون با پنج ردیف که هر کدام چهار آیتم دارند می‌سازد. ردیف آخر تنها سه آیتم دارد چون کلید صفر به اندازه دو کلید فضا اشغال می‌کند. کلیدها درون کلاس Keys تعریف می‌شوند که در ادامه بررسی خواهیم کرد و در این کلاس و بقیه بخش‌های دیگر اپلیکیشن به صورت صریح مورد ارجاع قرار می‌گیرند بدین ترتیب تعریف رفتار دقیق مطلوب در هر گام با استفاده بیشینه از ثابت‌های از قبل تعریف شده به امری سرراست تبدیل می‌شود.

کلاس KeySymbol

نخستین مفهوم در پس منطق keypad در فایل lib/key-symbol.dart قرار دارد:

1import 'package:flutter_calculator_demo/calculator-key.dart';
2
3enum KeyType { FUNCTION, OPERATOR, INTEGER }
4
5class KeySymbol {
6
7  const KeySymbol(this.value);
8  final String value;
9
10  static List<KeySymbol> _functions = [ Keys.clear, Keys.sign, Keys.percent, Keys.decimal ];
11  static List<KeySymbol> _operators = [ Keys.divide, Keys.multiply, Keys.subtract, Keys.add, Keys.equals ];
12
13  @override 
14  String toString() => value;
15
16  bool get isOperator => _operators.contains(this);
17  bool get isFunction => _functions.contains(this);
18  bool get isInteger => !isOperator && !isFunction;
19
20  KeyType get type => isFunction ? KeyType.FUNCTION : (isOperator ? KeyType.OPERATOR : KeyType.INTEGER);
21}

کلید KeySymbol یک پوشش برای آیکون‌های کلیدهای مختلف از قبیل نمادهای جمع و تفریق و اعداد تک‌رقمی (به عنوان رشته) ارائه می‌کند. در این کلاس یک لیست از کلیدهایی تعریف شده که «کلیدهای تابعی» (function keys) هستند و لیست دیگری نیز برای «کلیدهای عملگر» (operator keys) وجود دارد. تفاوت این دو لیست آن است که کلیدهای تابعی حالت محاسبه را به نوعی تغییر می‌دهند و کلیدهای عملگر عملیات‌های مقدماتی ریاضیاتی مانند تقسیم و ضرب را اجرا می‌کنند. یک accessor به نام type نوع یک کلید را بازگشت می‌دهد که تابعی یا عملگر و یا عدد صحیح است. این تشخیص از روی تعلق کلیدها به لیست‌های مختلف تعریف شده صورت می‌گیرد.

ویجت CalculatorKey

اکنون به بررسی تعاریف در فایل lib/calculator-key.dart می‌پردازیم:

1import 'package:flutter/material.dart';
2import 'package:flutter_calculator_demo/key-controller.dart';
3import 'package:flutter_calculator_demo/key-symbol.dart';
4
5abstract class Keys {
6
7  static KeySymbol clear = const KeySymbol('C');
8  static KeySymbol sign = const KeySymbol('±');
9  static KeySymbol percent = const KeySymbol('%');
10  static KeySymbol divide = const KeySymbol('÷');
11  static KeySymbol multiply = const KeySymbol('x');
12  static KeySymbol subtract = const KeySymbol('-');
13  static KeySymbol add = const KeySymbol('+');
14  static KeySymbol equals = const KeySymbol('=');
15  static KeySymbol decimal = const KeySymbol('.');
16
17  static KeySymbol zero = const KeySymbol('0');
18  static KeySymbol one = const KeySymbol('1');
19  static KeySymbol two = const KeySymbol('2');
20  static KeySymbol three = const KeySymbol('3');
21  static KeySymbol four = const KeySymbol('4');
22  static KeySymbol five = const KeySymbol('5');
23  static KeySymbol six = const KeySymbol('6');
24  static KeySymbol seven = const KeySymbol('7');
25  static KeySymbol eight = const KeySymbol('8');
26  static KeySymbol nine = const KeySymbol('9');
27}
28
29class CalculatorKey extends StatelessWidget {
30
31  CalculatorKey({ this.symbol });
32
33  final KeySymbol symbol;
34  
35  Color get color {
36
37    switch (symbol.type) {
38
39      case KeyType.FUNCTION:
40        return Color.fromARGB(255, 96, 96, 96);
41
42      case KeyType.OPERATOR:
43        return Color.fromARGB(255, 32, 96, 128);
44
45      case KeyType.INTEGER:
46      default:
47        return Color.fromARGB(255, 128, 128, 128);
48    }
49  }
50
51  static dynamic _fire(CalculatorKey key) => KeyController.fire(KeyEvent(key));
52
53  @override
54  Widget build(BuildContext context) {
55
56    double size = MediaQuery.of(context).size.width / 4;
57    TextStyle style = Theme.of(context).textTheme.display1.copyWith(color: Colors.white);
58
59    return Container(
60      
61      width: (symbol == Keys.zero) ? (size * 2) : size,
62      padding: EdgeInsets.all(2),
63      height: size,
64      child: RaisedButton(
65        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
66        color: color,
67        elevation: 4,
68        child: Text(symbol.value, style: style),
69        onPressed: () => _fire(this),
70      )
71    );
72  }
73}

نخستین کلاس در این فایل Keys است که به عنوان لیستی از کلیدهای موجود برای کار کردن در سراسر اپلیکیشن محسوب می‌شود. این کلیدها به صوت وهله‌هایی از KeySymbol تعریف شده‌اند که ماهت کلید را در سازنده می‌گیرند و امکان تعریف نماد کلید و شیءهای متناظر را به صورت ثابت‌هایی فراهم می‌سازند که می‌توان از آن‌ها به روشی شیءگرا در سراسر اپلیکیشن استفاده مجدد کرد.

کلاس CalculatorKey نیز در این فایل قرار دارد و یک ویجت بی‌حالت است که تنها یک KeySymbol به عنوان سازنده خود می‌گیرد. CalculatorKey می‌تواند رنگ پس‌زمینه و عرض کلید خود را بر اساس نوع کلید تشخیص دهد. کلیدهای تابعی، عملگر و عدد صحیح، هر کدام رنگ‌های خاص خود را دارند در حالی که کلید صفر دارای عرضی دو برابر کلیدهای نرمال است.

رویدادهای فشردن کلید به وسیله calling _fire مدیریت می‌شود که به نوبه خود رویدادی روی KeyController صادر می‌کند و این رویداد به هر شیئی که به استریم آن گوش می‌دهد ارسال می‌شود.

کلاس KeyController

فایل بعدی که بررسی می‌کنیم به کنترلر کلید مربوط است که در مسیر key-controller.dart قرار دارد:

1import 'dart:async';
2import 'package:flutter_calculator_demo/calculator-key.dart';
3
4class KeyEvent {
5
6  KeyEvent(this.key);
7  final CalculatorKey key;
8}
9
10abstract class KeyController {
11
12  static StreamController _controller = StreamController();
13  static Stream get _stream => _controller.stream;
14
15  static StreamSubscription listen(Function handler) => _stream.listen(handler as dynamic);
16  static void fire(KeyEvent event) => _controller.add(event);
17
18  static dispose() => _controller.close();
19}

در این فایل دو کلاس وجود دارد:

  • KeyEvent - پوششی برای CalculatorKey است که برای سهولت کار فشرده شده است.
  • KeyController - پیام‌رسانی رویداد کلید را از طریق Stream و KeyEvent مدیریت می‌کند.

KeyController یک وهله محلی از StreamController و یک ارجاع به استریم درونی آن تنظیم می‌کند و سه متد را ارائه می‌دهد:

  • Listen – به شیء امکان ثبت نام در پیام‌های KeyEvent را می‌دهد.
  • Fire – به شیء امکان ارسال پیام‌های KeyEvent را می‌دهد.
  • Dispose – به اپلیکیشن امکان بستن ملایم StreamController را می‌دهد.

بدین ترتیب با یک نمونه مقدماتی از الگوی «انتشار-ثبت نام» (publish-subscribe) مواجه هستیم که در آن دغدغه شیوه اتصال کامپوننت‌ها به هم از کمترین اهمیت برخوردار است، چون تأثیر اندکی روی خود کامپوننت‌ها دارد. با بهره‌گیری از استریم‌ها و کنترل‌ها امکان افزودن کامپوننت‌های بیشتر و واداشتن آن‌ها به گوش دادن برای اطلاعاتی که نیاز دارند و سپس فراخوانی setState در موارد نیاز و تغییر دادن حالت بر مبنای اطلاعات جدید ارسالی از استریم رویداد که به آن گوش می‌دهد به کاری ساده تبدیل شده است. به طور مشابه همین کامپوننت می‌تواند بدون هیچ تأثیری روی بقیه اپلیکیشن حذف شود، چون داده‌ها به درستی در بقیه بخش‌های برنامه با/بدون شنونده اضافی گردش می‌یابند.

برای کسب اطلاعات بیشتر در مورد برنامه‌نویسی مبتنی بر رویداد ناهمگام با استریم‌ها در دارت می‌توانید به صفحه مستندات (+) آن مراجعه کنید.

کلاس Processor

در انتهای این بخش به بررسی فایلی می‌پردازیم که بار سنگینی بر دوش آن قرار دارد.

این فایل processor.dart است:

1import 'dart:async';
2import 'package:flutter_calculator_demo/calculator-key.dart';
3import 'package:flutter_calculator_demo/key-controller.dart';
4import 'package:flutter_calculator_demo/key-symbol.dart';
5
6abstract class Processor {
7
8  static KeySymbol _operator;
9  static String _valA = '0';
10  static String _valB = '0';
11  static String _result;
12
13  static StreamController _controller = StreamController();
14  static Stream get _stream => _controller.stream;
15
16  static StreamSubscription listen(Function handler) => _stream.listen(handler);
17  static void refresh() => _fire(_output);
18
19  static void _fire(String data) => _controller.add(_output);
20
21  static String get _output => _result == null ? _equation : _result;
22
23  static String get _equation => _valA
24    + (_operator != null ? ' ' + _operator.value : '')
25    + (_valB != '0' ? ' ' + _valB : '');
26
27  static dispose() => _controller.close();
28
29  static process(dynamic event) {
30    
31    CalculatorKey key = (event as KeyEvent).key;
32    switch(key.symbol.type) {
33
34      case KeyType.FUNCTION:
35        return handleFunction(key);
36
37      case KeyType.OPERATOR:
38        return handleOperator(key);
39
40      case KeyType.INTEGER:
41        return handleInteger(key);
42    }
43  }
44
45  static void handleFunction(CalculatorKey key) {
46
47    if (_valA == '0') { return; }
48    if (_result != null) { _condense(); }
49
50    Map<KeySymbol, dynamic> table = {
51      Keys.clear: () => _clear(),
52      Keys.sign: () => _sign(),
53      Keys.percent: () => _percent(),
54      Keys.decimal: () => _decimal(),
55    };
56
57    table[key.symbol]();
58    refresh();
59  }
60
61  static void handleOperator(CalculatorKey key) {
62
63    if (_valA == '0') { return; }
64    if (key.symbol == Keys.equals) { return _calculate(); }
65    if (_result != null) { _condense(); }
66
67    _operator = key.symbol;
68    refresh();
69  }
70
71  static void handleInteger(CalculatorKey key) {
72
73    String val = key.symbol.value;
74    if (_operator == null) { _valA = (_valA == '0') ? val : _valA + val; }
75    else { _valB = (_valB == '0') ? val : _valB + val; }
76    refresh();
77  }
78
79  static void _clear() {
80
81    _valA = _valB = '0'; 
82    _operator = _result = null;
83  }
84
85  static void _sign() {
86
87    if (_valB != '0') { _valB = (_valB.contains('-') ? _valB.substring(1) : '-' + _valB); }
88    else if (_valA != '0') { _valA = (_valA.contains('-') ? _valA.substring(1) : '-' + _valA); }
89  }
90
91  static String calcPercent(String x) => (double.parse(x) / 100).toString();
92
93  static void _percent() {
94
95    if (_valB != '0' && !_valB.contains('.')) { _valB = calcPercent(_valB); }
96    else if (_valA != '0' && !_valA.contains('.')) { _valA = calcPercent(_valA); }
97  }
98
99  static void _decimal() {
100
101    if (_valB != '0' && !_valB.contains('.')) { _valB = _valB + '.'; }
102    else if (_valA != '0' && !_valA.contains('.')) { _valA = _valA + '.'; }
103  }
104
105  static void _calculate() {
106
107    if (_operator == null || _valB == '0') { return; }
108
109    Map<KeySymbol, dynamic> table = {
110      Keys.divide: (a, b) => (a / b),
111      Keys.multiply: (a, b) => (a * b),
112      Keys.subtract: (a, b) => (a - b),
113      Keys.add: (a, b) => (a + b)
114    };
115
116    double result = table[_operator](double.parse(_valA), double.parse(_valB));
117    String str = result.toString();
118
119    while ((str.contains('.') && str.endsWith('0')) || str.endsWith('.')) { 
120      str = str.substring(0, str.length - 1); 
121    }
122
123    _result = str;
124    refresh();
125  }
126
127  static void _condense() {
128
129    _valA = _result;
130    _valB = '0';
131    _result = _operator = null;
132  }
133}

کلاس Processor جایی است که اغلب منطق برنامه برای مدیریت تعامل‌های کی‌پد و اجرای محاسبات واقعی در آن قرار دارد. در آغاز چهار مشخصه اصلی روی کلاس برای ذخیره‌سازی متغیرهای ورودی و خروجی داریم:

  • operator_: عملگر ریاضیاتی درخواست شده جاری را نگهداری می‌کند.
  • valA_: عملوند سمت چپ عملگر را نگهداری می‌کند.
  • valB_: عملوند سمت راست عملگر را نگهداری می‌کند.
  • result_: نتیجه محاسبه قبلی را نگهداری می‌کند.

در همین کلاس پیاده‌سازی دیگری از استریم/کنترل استریم وجود دارد که کلاس Calculator از آن برای ثبت نام در پیام‌هایی استفاده می‌کند که شامل مقدار نمایشی به‌روزرسانی شده است. تفاوت اصلی در این جا آن است که متد fire_ با یک کاراکتر زیرخط در ابتدا اعلان شده است و آن را در این کلاس به یک متد خصوصی تبدیل کرده است. این وضعیت مطلوبی است زیرا این تنها جایی است که مقدار واقعی نتیجه محاسبه می‌شود. همچنین یک متد ساده refresh نیز وجود دارد که مقدار خروجی کنونی را برای کامپوننت‌هایی که گوش می‌دهند ارسال می‌کند تا حالت را تنظیم کرده و view را به‌روزرسانی کنند. این متد به صورت عمومی تعریف شده تا امکان گوش دادن کامپوننت‌هایی که درخواست به‌روزرسانی دارند فراهم شود. برای نمونه این اتفاق زمانی که فرایند مقداردهی تکمیل شود رخ می‌دهد.

در ادامه دو مشخصه accessor وجود دارند که محتوای نمایشی را تعیین می‌کنند:

  • output_ در صورتی که null نباشد، result_ و در غیر این صورت equation_ را بازگشت می‌دهد.
  • equation_ در صورت آماده بودن operator_ معادله و در غیر این صورت valA_ را بازگشت می‌دهد.

ترکیب accessor-های زنجیر شده، یک روش ساده برای کنترل خروجی ماشین حساب است و بسته به این که ماشین حساب در چه حالی قرار دارد، یا به نمایش result_ محاسبه قبلی، یعنی محاسبه‌ای که وارد شده و آماده حل شدن است و یا مقدار انتهای سمت چپ می‌پردازد.

این کلاس یک متد dispose نیز دارد که امکان خروج از اپلیکیشن را فراهم می‌کند. در بخش بعدی گروهی از متدها وجود دارند که رویدادهای ورودی کلید را مدیریت می‌کنند:

  • Process – اقدام بعدی که باید اجرا شود را بر اساس نوع KeySymbol انتخاب می‌کند.
  • handleFunction آماده اجرای نوع تابع روی حالت کنونی می‌شود.
  • handleOperator – عملگر ریاضیاتی منتخب را به operator_ انتساب می‌دهد.
  • handleInteger – مقدار عدد کلید را به عملوند مناسب اضافه می‌کند.

متد process جایی است که رویدادها از KeyController به وسیله Calculator به آن فوروارد می‌شوند. این متد از نوع نماد کلید برای انتخاب این که باید یک تابع را اجرا کند، یک عملگر را ذخیره کند یا ورودی عددی را پردازش کند بهره می‌گیرد.

متد handleFunction ابتدا بررسی می‌کند که آیا عملوند سمت چپ (valA_) صفر است یا نه و در چنین حالتی رویداد را کنار می‌گذارد. سپس بررسی می‌کند تا بفهمد آیا باید نتیجه معادله قبلی را با فراخوانی condense_ در عملوند سمت چپ ذخیره کند تا عملیات بعدی بتواند وارد شود یا نه. سپس یک نقشه از تابع‌ها برای انتخاب تابع مطلوب بر مبنای KeySymbol ورودی KeyEvent استفاده می‌شود و KeySymbol به تابع مناسب پس از refresh ارسال می‌شود. این وضعیت عموماً تمیزتر از زنجیره گزاره‌های if-else است و این سطح از گویایی یک قابلیت بارز زبان‌های مدرنی مانند دارت محسوب می‌شود.

متد handleOperator نیز بررسی می‌کند آیا عملوند چپ صفر است یا نه و در این صورت رویدادها را رد می‌کند و نتیجه محاسبه قبلی را در صورت ضرورت در عملوند چپ ذخیره می‌کند. سپس عملوند انتخابی را در operator_ ذخیره کرده و refresh را فراخوانی می‌کند.

متد handleInteger ورودی عددی را در صورت عدم وجود operator_ در عملوند چپ و در غیر این صورت در عملوند راست دریافت می‌کند. در پایان refresh را فراخوانی می‌کند.

گروه بعدی از متدها در کلاس به مدیریت ورودی تابع از کی‌پد می‌پردازند تا حالت پردازنده و مقدار آن را تغییر دهند:

  • clear_: ماشین حساب را ریست کرده و refresh را فرا می‌خواند.
  • sign_: علامت عملوندی را که در حال حاضر ورودی را دریافت می‌کند تغییر می‌دهد.
  • percent / calcPercent_: مقدار عملوند کنونی را بر 100 تقسیم می‌کند.
  • decimal_: یک نقطه اعشاری به انتهای عملوند کنونی الحاق می‌کند.
  • calculate_: محاسبه واقعی را اجرا کرده و نتیجه را در result_ ذخیره می‌کند.
  • result_: اقدام به ذخیره‌سازی result_ در عملوند چپ و آماده‌سازی ورودی بیشتر می‌کند.

متد clear_ به سادگی ماشین حساب را روی مقدار صفر ریست می‌کند. متد sign_ تعیین می‌کند که کدام عملوند باید تغییر یابد و آیا باید یک کاراکتر – به آن اضافه یا کسر شود. متدهای percent_ و decimal_ عمدتاً به طریق مشابهی عمل می‌کنند و عملوند صحیحی را انتخاب و وظایف مربوطه را اجرا می‌کنند.

متد calculate_ هنگامی که هیچ عملگری یا مقدار سمت راست برای اجرای محاسبه روی آن نمانده باشد رویدادها را رد می‌کند و در غیر این صورت از یک نقشه تابع برای انتخاب فرمولی که باید استفاده کرد، محاسبه نتیجه و حذف هر گونه صفر ابتدایی، ذخیره‌سازی مقدار در result_ و در نهایت اجرای refresh بهره می‌گیرد.

سخن پایانی

بدین ترتیب به انتهای بررسی سورس کد این اپلیکیشن ماشین حساب می‌رسیم که کد کامل آن را می‌توانید در این ریپوی گیت‌هاب (+) ملاحظه کنید:

اپلیکیشن ماشین حساب با فلاتر

این ماشین حساب ساده صرفاً با چند صد خط کد پیاده‌سازی شده است و قدرت گویایی که هنگام ساخت اپلیکیشن‌های موبایل با دارت و فلاتر در اختیار ما قرار می‌گیرد را نشان می‌دهد. این قدرت ما را ترغیب می‌کند که از الگوهای طراحی تمیز، استفاده مجدد و مؤثر از کد، ایمنی نوع صریح، و دیگر عادت‌های توسعه‌ای مناسب بهره بگیریم.

همه این قابلیت‌ها موجب شده که فلاتر به گزینه‌ای عالی برای ساخت اپلیکیشن‌های موبایل با کیفیت بالا تبدیل شود که با سرعت بالایی بارگذاری و اجرا می‌شوند، تنها به مقدار نیاز از توان پردازشی و حافظه دستگاه بهره می‌گیرند، تأخیر یا توقف غیرمنتظره‌ای ندارند و می‌توانند به دلیل معماری و کدنویسی تمیز، در آینده به سادگی به‌روزرسانی و نگهداری شوند.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۴ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
itnext
۱ دیدگاه برای «ساخت اپلیکیشن ماشین حساب با فلاتر — از صفر تا صد»

سلام.
من وقتی برنامه رو اجرا می کنم، ماشین حساب نمایش داده میشه ولی با کلیک روی اعداد هیچ اتفاقی نمیوفته.
مشکل از کجاست؟
ممنون

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *