王奕涵(Yihan Wang)FZUID:832402121 MUID:24125393

832402121 王奕涵 2026-09-27 12:41:34

Yihan Wang Calculator

Front-End and Back-End Separation Calculator System

王奕涵(Yihan Wang)FZUID:832402121 MUID:24125393

Course Information

ItemDescription
CourseSoftware Engineering
AssignmentFirst Assignment: Front-End and Back-End Separation Calculator System
Objectives of This AssignmentTo understand and implement front-end/back-end separation through a native Android client and an independent HTTP API; to develop safe expression parsing, persistent calculation history, and API-based deletion; and to practise testing, deployment, and technical documentation.
Other ReferencesAndroid Developers documentation, Python documentation, SQLite documentation, Google Java Style Guide, PEP 8, and PEP 257.
StudentYihan Wang
Front EndNative Android application written in Java
Back EndPython standard-library HTTP API
DatabaseSQLite
DeploymentUbuntu 22.04, Nginx, and systemd
Public APIhttp://8.217.14.75/api

This assignment required the development of a calculator system based on a front-end and back-end separation architecture. The Android front end is responsible for user interaction and information display. The Python back end is responsible for expression validation, parsing, calculation, exception handling, database operations, and history management.

The final result is an Android calculator named Yihan Wang Calculator. The Android client communicates with an independently deployed Python API through HTTP and JSON. The final result is always calculated by the backend and returned to the Android application.


Table of Contents

1. Repository Links and Code Standards

Android Front-End Repository

Python Back-End Repository

Android Code Standards

Python Code Standards

Android APK Download

2. Assignment Requirements

3. PSP Table

4. Presentation of the Finished Product

Figure 1: Main Android Interface

Figure 2: Basic Addition

Figure 3: Basic Subtraction

Figure 4: Basic Multiplication

Figure 5: Basic Division

Figure 6: Operator Precedence

Figure 7: Parentheses

Figure 8: Decimal Calculation

Figure 9: Unary Negative Number

Figure 10: Invalid Expression

Figure 11: Division by Zero

Figure 12: History After Restart

Figure 13: Delete One Record

Figure 14: Clear All History

Figure 15: Public Backend Health Check

Figure 16: Backend History Endpoint

Figure 17: Android GitHub Repository

Figure 18: Backend GitHub Repository

Extended and Supporting Features

5. Requirement Analysis

Frontend responsibilities

Backend responsibilities

6. System Architecture

7. Functional Structure

8. API Design

Calculation request

Error response

History response

9. Database Design

Table fields

10. Code Explanation and Implementation Details

10.1 Android Request Handling

10.2 Backend Calculation Handler

10.3 Recursive-Descent Parser

10.4 SQLite Operations

11. Android Installation and Testing

11.1 Run with Android Studio

11.2 Build and Install a Debug APK

11.3 Local Backend Testing

12. Backend Deployment

13. Testing Results

13.1 Parser Tests

13.2 Invalid Input Tests

13.3 Division-by-Zero Test

13.4 Persistence Test

13.5 Deletion Test

13.6 Clear-All Test

13.7 Front-End and Back-End Separation Test

14. Personal Journey: Problems Encountered and Solutions

Problem 1: Gradle Build Configuration

Problem 2: HTTP Access from Android

Problem 3: Backend Connectivity

Problem 4: Expression Parsing

Problem 5: Persistent History

Problem 6: Two-Repository Submission

15. Personal Learnings

16. Future Improvements

17. Conclusion


1. Repository Links and Code Standards

The front-end and back-end projects are stored in two independent GitHub repositories, as required by the assignment.

Android Front-End Repository

https://github.com/3785107480/EE308FZ_Yihan_Wang_android_frontend

Python Back-End Repository

https://github.com/3785107480/EE308FZ_Yihan_Wang_backend

Android Code Standards

https://github.com/3785107480/EE308FZ_Yihan_Wang_android_frontend/blob/main/codestyle.md

The Android coding conventions are based on the Google Java Style Guide and Android API guidelines.

Python Code Standards

https://github.com/3785107480/EE308FZ_Yihan_Wang_backend/blob/main/codestyle.md

The Python coding conventions are based on PEP 8 and PEP 257.

Android APK Download

https://github.com/3785107480/EE308FZ_Yihan_Wang_android_frontend/releases/tag/v1.0.0

The release contains the tested debug APK. The APK is intended for assignment demonstration and evaluation.


2. Assignment Requirements

The assignment requires a calculator system with a separated front end and back end.

The major requirements are:

  • Support addition, subtraction, multiplication, and division.

  • Support compound mathematical expressions.

  • Support operator precedence.

  • Support parentheses.

  • Support unary positive and negative numbers.

  • Support decimal numbers.

  • Reject invalid expressions.

  • Reject division by zero.

  • Perform the final calculation on the backend.

  • Store successful calculations in a backend database.

  • Display calculation history in the frontend.

  • Preserve history after restarting the frontend.

  • Delete a selected calculation history record.

  • Provide a publicly accessible backend service.

  • Submit the frontend and backend projects in separate GitHub repositories.

  • Provide code standards for both projects.

  • Provide an English project blog with screenshots, PSP data, design explanations, and testing instructions.

The most important architectural requirement is that the Android client must not calculate the final result locally.

The required flow is:

text

Android sends expression
        |
        v
Python backend validates and parses expression
        |
        v
Python backend calculates the result
        |
        v
Python backend stores the successful calculation
        |
        v
Python backend returns the result
        |
        v
Android displays the backend result

The Android client sends an expression such as:

{
  "expression": "1+2*3"
}

The Android client does not send a pre-calculated result.


3. PSP Table

The Planned Time column was recorded before implementation began, and the Actual Time column was updated after each corresponding development or testing phase was completed. The planned total effort was 1,950 minutes, while the actual total effort was 2,430 minutes. The additional time was mainly spent on Android–backend integration, error handling, testing and debugging, deployment configuration, and documentation with screenshots. Overall, the project took approximately two calendar weeks: one week focused mainly on the Android frontend and one week focused mainly on the Python backend, SQLite persistence, deployment, and testing.

ActivityPlanned Time (min)Actual Time (min)Description
Requirement analysis90120Studied the assignment and identified functional requirements
System architecture design90120Designed the Android, API, and SQLite architecture
Backend parser and API300360Implemented the parser, HTTP endpoints, and validation
SQLite persistence150180Implemented database initialization, insertion, query, and deletion
Android UI development360420Developed the calculator interface and interaction logic
Android and backend integration180240Connected the Android client to the public API
Error handling120150Implemented invalid-expression and division-by-zero handling
Testing and debugging240300Tested calculations, history persistence, deletion, and deployment
Deployment configuration180240Configured Ubuntu, Nginx, and systemd
Documentation and screenshots240300Prepared the English blog, screenshots, and testing instructions
Total19502430Approximately two weeks of development

The actual time was higher than the initial estimate because Android networking, Gradle configuration, cloud deployment, and evidence preparation required additional debugging.


4. Presentation of the Finished Product

The finished product is a native Android calculator named Yihan Wang Calculator.

The user interface contains:

  • Application title.
  • Backend connection status.
  • Expression display.
  • Result display.
  • Calculator keypad.
  • Calculation history.
  • Single-record delete buttons.
  • Clear-all history button.
  • English error messages.

Figure 1: Main Android Interface

Description:

The screenshot shows the main Android interface. The title is Yihan Wang Calculator, the connection status displays API connected, and the calculator shows the compound expression 1+2*3 with the backend result 7.

Figure 2: Basic Addition

Description:

The Android client sends the expression 12+8 to the backend. The backend calculates the result and returns 20.

Figure 3: Basic Subtraction

Description:

The subtraction expression is sent to the backend and the returned result is displayed by Android.

Figure 4: Basic Multiplication

Description:

The multiplication expression is calculated by the backend and displayed by the Android client.

Figure 5: Basic Division

Description:

The division operation is processed by the backend and the result is returned to Android.

Figure 6: Operator Precedence

 

Description:

The expression 1+2*3 returns 7, proving that multiplication is performed before addition.

Figure 7: Parentheses

Description:

The expression (1+2)*3 returns 9, proving that parentheses change the normal operation order.

Figure 8: Decimal Calculation

Description:

The expression 10/4 returns 2.5, demonstrating decimal calculation and floating-point result handling.

Figure 9: Unary Negative Number

Description:

The expression 3*-2 returns -6, demonstrating support for unary negative numbers after a multiplication operator.

Figure 10: Invalid Expression

Description:

The expression 2**3 is rejected by the backend. The Android client displays the English error message and does not add the invalid expression to calculation history.

Figure 11: Division by Zero

Description:

The expression 8/0 is rejected by the backend. The application displays Division by zero is not allowed. No failed calculation is stored in the database.

Figure 12: History After Restart

Description:

After restarting the Android application, previously stored records remain visible. This proves that history is retrieved from the backend SQLite database instead of temporary frontend memory.

Figure 13: Delete One Record

Description:

The selected history record has been deleted. The remaining record is still displayed. The Android client reloads history from the backend after the deletion request.

Figure 14: Clear All History

Description:

After selecting Clear all, the history list becomes empty and displays No calculations yet.

Figure 15: Public Backend Health Check

Description:

The browser accesses:

http://8.217.14.75/api/health

The backend returns:

{"status":"ok"}

This proves that the deployed API is publicly accessible.

Figure 16: Backend History Endpoint

Description:

The browser accesses:

http://8.217.14.75/api/history

The response contains expressions, results, record IDs, and UTC timestamps stored in the backend database.

Figure 17: Android GitHub Repository

Description:

The Android repository contains the Gradle project, Android source code, README, and codestyle.md.

Figure 18: Backend GitHub Repository

Description:

The backend repository contains the Python service, deployment configuration, tests, documentation, README, and codestyle.md.

Extended and Supporting Features

Clear All History

In addition to deleting an individual calculation history record, the application provides a Clear all function. When the user selects Clear all, the Android client sends a DELETE /api/history request. The backend deletes all records from the SQLite calculations table and returns a confirmation response. The Android client then requests the latest history through GET /api/history. Figure 14 shows that the history list becomes empty and displays “No calculations yet”.

Backend Connection Status

When the Android activity starts, the client sends a request to GET /api/health. If the backend returns HTTP 200 with {"status":"ok"}, the application displays “API connected”. If the request fails, the application displays “API offline”. This is a startup connectivity check rather than a continuous background monitoring system.

Public backend deployment and APK distribution are described in the deployment and installation sections. They support project evaluation and installation rather than being separate calculator algorithms.


5. Requirement Analysis

The system was divided into frontend responsibilities and backend responsibilities.

Frontend responsibilities

The Android frontend is responsible for:

  • Displaying the calculator user interface.
  • Receiving button input.
  • Building the expression string.
  • Converting display symbols into API symbols.
  • Sending HTTP requests.
  • Displaying backend results.
  • Displaying backend error messages.
  • Loading calculation history.
  • Sending deletion requests.
  • Refreshing history after deletion.

The Android frontend does not perform the core calculation.

For example, the Android client converts the display symbols:

× -> *
÷ -> /
− -> -

This is only input normalization. The Android client does not evaluate the expression.

Backend responsibilities

The Python backend is responsible for:

  • Receiving HTTP requests.
  • Parsing JSON request bodies.
  • Validating expressions.
  • Tokenizing mathematical input.
  • Applying operator precedence.
  • Handling parentheses.
  • Handling unary plus and minus.
  • Calculating the final result.
  • Detecting division by zero.
  • Rejecting invalid input.
  • Persisting successful calculations.
  • Returning calculation history.
  • Deleting individual records.
  • Clearing all records.
  • Returning standardized JSON responses.

6. System Architecture

The system contains three main layers:

+-----------------------------------------------------------+
| Android Client                                            |
|                                                           |
| Calculator UI | Result Display | History Display          |
| HttpURLConnection | JSON request and response handling    |
+-----------------------------------------------------------+
                            |
                            | HTTP / JSON
                            v
+-----------------------------------------------------------+
| Python Backend                                            |
|                                                           |
| http.server request handlers                              |
| Recursive-descent expression Parser                       |
| Validation and error handling                             |
+-----------------------------------------------------------+
                            |
                            | SQL statements
                            v
+-----------------------------------------------------------+
| SQLite Database                                           |
|                                                           |
| calculations table                                        |
| expression | result | created_at                          |
+-----------------------------------------------------------+

The backend is deployed on an Ubuntu 22.04 server.

Nginx provides the public /api/ path and forwards requests to the Python service running on 127.0.0.1:8000.

The Android application uses:

http://8.217.14.75/api

as the configured backend base URL.


7. Functional Structure

Calculator System
|
|-- Calculator Module
|   |-- Addition
|   |-- Subtraction
|   |-- Multiplication
|   |-- Division
|   |-- Compound Expressions
|   |-- Parentheses
|   |-- Decimal Numbers
|   |-- Unary Plus and Minus
|   |-- Invalid Expression Handling
|   |-- Division by Zero Handling
|
|-- Calculation History Module
|   |-- Save Successful Calculation
|   |-- Query History
|   |-- Delete One Record
|   |-- Clear All Records
|   |-- Persist Data in SQLite
|
|-- Android Front End
|   |-- Calculator Button Input
|   |-- Result Display
|   |-- Error Toast Display
|   |-- History Display
|   |-- Backend Connection Status
|
|-- Python Back End
    |-- HTTP API
    |-- JSON Request Parsing
    |-- Recursive-Descent Parser
    |-- Calculation Logic
    |-- SQLite Operations

8. API Design

The backend API uses JSON over HTTP.

The public base URL is:

http://8.217.14.75/api
MethodEndpointPurposeSuccess ResponseError Response
GET/api/healthCheck service availability{"status":"ok"}-
POST/api/calculateCalculate and save an expressionHTTP 201 with dataHTTP 400 with error
GET/api/historyRead history recordsHTTP 200 with data-
DELETE/api/history/{id}Delete one recordHTTP 200 with messageHTTP 404 if missing
DELETE/api/historyClear all recordsHTTP 200 with messageNot applicable

Calculation request

POST /api/calculate
Content-Type: application/json

Request body:

{
  "expression": "1+2*3"
}

Successful response:

{
  "data": {
    "id": 1,
    "expression": "1+2*3",
    "result": 7,
    "created_at": "2026-09-26T13:09:18+00:00"
  }
}

Error response

For division by zero:

{
  "error": "Division by zero is not allowed."
}

For an invalid expression:

{
  "error": "A number or parenthesized expression was expected."
}

History response

{
  "data": [
    {
      "id": 1,
      "expression": "1+2*3",
      "result": 7,
      "created_at": "2026-09-26T13:09:18+00:00"
    }
  ]
}

If there is no history:

{
  "data": []
}

The client checks the HTTP status code and interprets the JSON response according to the endpoint. Calculation responses contain a data object, history responses contain a data array, deletion responses contain a message, and the health endpoint returns a status field. Handled application-level API errors contain an error field. Network failures and errors generated by the reverse proxy may not use this JSON format.

An empty history list is a successful result rather than an error. Therefore, GET /api/history returns HTTP 200 with {"data":[]} when no records exist.


9. Database Design

SQLite is used as the backend database.

The SQLite database is created automatically when the Python server starts. The database file is created beside the running server.py file.

During local development, the database path is:

backend/calculator.db

In the documented Ubuntu deployment, the server file is copied to:

/opt/orbit-calculator/server.py

Therefore, the deployed database path is:

/opt/orbit-calculator/calculator.db

The database path is determined by the location of the running server file. The table is initialized automatically at startup, so no manual database import is required.

The table is created automatically with the following schema:

CREATE TABLE IF NOT EXISTS calculations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    expression TEXT NOT NULL,
    result REAL NOT NULL,
    created_at TEXT NOT NULL
);

Table fields

FieldTypeDescription
idINTEGERAuto-increment primary key
expressionTEXTOriginal calculation expression
resultREALBackend-generated calculation result
created_atTEXTUTC ISO 8601 calculation time

Only successful calculations are stored.

Invalid expressions and division-by-zero requests return an error before the database insertion step. Therefore, failed requests do not create history records.

The database is the source of truth for calculation history. The Android application does not use local storage as the primary history store.


10. Code Explanation and Implementation Details

10.1 Android Request Handling

The Android client uses HttpURLConnection for HTTP communication and org.json for JSON request and response processing.

The following method shows the calculation request flow, reformatted for readability. It depends on the request helpers and UI components defined in MainActivity.java.

private void calculate() {
    if (expression.length() == 0) {
        return;
    }

    resultView.setText("…");

    try {
        JSONObject payload = new JSONObject();
        payload.put("expression", expression.toString());

        postJson(
            "/calculate",
            payload,
            response -> {
                try {
                    JSONObject data =
                        response.getJSONObject("data");

                    resultView.setText(
                        String.valueOf(data.get("result"))
                    );

                    expressionView.setText(
                        data.getString("expression")
                    );

                    loadHistory();
                } catch (Exception error) {
                    showError(error.getMessage());
                }
            },
            this::showError
        );
    } catch (Exception error) {
        showError(error.getMessage());
    }
}

The client sends only the expression field. For example, it sends {"expression":"1+2*3"} rather than sending a pre-calculated result.

The backend validates and evaluates the expression, stores the successful calculation, and returns the record. Android reads data.result, displays the returned value, and requests the latest history.

The request helper runs network operations on a background executor. This prevents a slow network request from blocking calculator button interaction or other UI operations.

The complete request method configures a 5,000-millisecond connection timeout and a 5,000-millisecond read timeout. It writes the request body as JSON, checks the HTTP status code, and reads either the normal response stream or the error stream.

For handled API failures, the client reads the error field and displays its message. Network failures are also passed to the error callback. Callbacks that update the interface are dispatched to the Android main thread.

When a calculation fails while the result display shows the loading indicator, the display resets to 0. This is a UI reset value, not a successful calculation result.

The Android client does not evaluate mathematical expressions locally. Converting × to *, ÷ to /, and − to - is input normalization only.

10.2 Backend Calculation Handler

The backend is implemented in backend/server.py. It uses Python standard-library modules, including http.server, sqlite3, json, math, re, and datetime.

ThreadingHTTPServer accepts HTTP requests, and APIHandler implements the endpoint handlers. The following calculation handler has been reformatted for readability. Imports, constants, the Parser class, and the json_response helper are defined elsewhere in the same file.

def do_POST(self):
    if urlparse(self.path).path != "/api/calculate":
        json_response(
            self,
            404,
            {"error": "Endpoint not found."},
        )
        return

    try:
        length = int(self.headers.get("Content-Length", 0))

        if length > MAX_REQUEST_BYTES:
            raise ExpressionError("Request body is too large.")

        data = json.loads(self.rfile.read(length))

        if not isinstance(data, dict):
            raise ExpressionError(
                "Request body must be a JSON object."
            )

        expression = data.get("expression")
        result = format_result(Parser(expression).parse())

        timestamp = datetime.now(timezone.utc).isoformat(
            timespec="seconds"
        )

        with sqlite3.connect(DB_PATH) as db:
            record_id = db.execute(
                """
                INSERT INTO calculations
                (expression, result, created_at)
                VALUES (?, ?, ?)
                """,
                (expression.strip(), result, timestamp),
            ).lastrowid

        json_response(
            self,
            201,
            {
                "data": {
                    "id": record_id,
                    "expression": expression.strip(),
                    "result": result,
                    "created_at": timestamp,
                }
            },
        )

    except (
        ValueError,
        TypeError,
        json.JSONDecodeError,
        sqlite3.Error,
    ) as error:
        json_response(
            self,
            400,
            {"error": str(error) or "Invalid request."},
        )

The handler first verifies the endpoint. It then reads the declared request-body length and rejects a body larger than MAX_REQUEST_BYTES, which is set to 4,096 bytes.

The request must contain a JSON object. The expression field is passed to Parser, which checks its type, length, characters, and mathematical syntax.

Calculation occurs before database insertion. Therefore, an invalid expression or division-by-zero error prevents the INSERT operation from being reached.

A successful request returns HTTP 201 with a data object containing the record ID, expression, result, and UTC timestamp. The calculation handler returns HTTP 400 with an error field for the exceptions handled by its error branch.

The INSERT statement uses placeholders and a separate parameter tuple. User-provided expression text is stored as data rather than concatenated into executable SQL.

10.3 Recursive-Descent Parser

The backend implements a custom recursive-descent parser. No third-party expression-evaluation library is required.

User expressions are not passed to eval, exec, shell commands, or another general-purpose code execution mechanism.

The parser uses the following grammar:

expression -> term (("+" | "-") term)*
term       -> factor (("*" | "/") factor)*
factor     -> number
            | "(" expression ")"
            | "+" factor
            | "-" factor

The expression level processes addition and subtraction. The term level processes multiplication and division. The factor level processes numbers, parentheses, and unary signs.

Because expression calls term, multiplication and division are evaluated before addition and subtraction. Operators at the same level are processed from left to right.

The following excerpt shows the main parsing methods. Tokenization, peek, and take are omitted; their complete implementations are available in backend/server.py.

def parse(self):
    result = self.expression()

    if self.index != len(self.tokens):
        raise ExpressionError(
            "Unexpected token in expression."
        )

    if not math.isfinite(result):
        raise ExpressionError(
            "Result is outside the supported numeric range."
        )

    return result

def expression(self):
    result = self.term()

    while self.peek("+") or self.peek("-"):
        operator = self.take()[1]
        right = self.term()

        if operator == "+":
            result += right
        else:
            result -= right

    return result

def term(self):
    result = self.factor()

    while self.peek("*") or self.peek("/"):
        operator = self.take()[1]
        right = self.factor()

        if operator == "/":
            if right == 0:
                raise ExpressionError(
                    "Division by zero is not allowed."
                )
            result /= right
        else:
            result *= right

    return result

def factor(self):
    if self.peek("+"):
        self.take("+")
        return self.factor()

    if self.peek("-"):
        self.take("-")
        return -self.factor()

    if self.peek("("):
        self.take("(")
        result = self.expression()
        self.take(")")
        return result

    if (
        self.index < len(self.tokens)
        and self.tokens[self.index][0] == "number"
    ):
        return float(self.take()[1])

    raise ExpressionError(
        "A number or parenthesized expression was expected."
    )

For 1+2*3, the term method evaluates 2*3 before expression performs the addition, producing 7.

For (1+2)*3, factor recursively processes the expression inside the parentheses. The parenthesized result is then multiplied by 3, producing 9.

Unary signs are handled recursively in factor. This supports expressions such as +5, -5+8, and 3*-2.

The tokenizer accepts decimal numbers, parentheses, and the operators +, -, *, and /. Whitespace is allowed between tokens. Unsupported characters are rejected, and invalid token sequences such as 2**3 fail during parsing.

Expressions must be non-empty strings no longer than 200 characters. The parser checks that all tokens have been consumed and that the final result is finite.

The backend uses floating-point arithmetic. The format_result helper returns suitable whole-number results as integers and otherwise rounds results to 12 decimal places. This is a general-purpose assignment calculator, not an arbitrary-precision financial calculation system.

10.4 SQLite Operations

SQLite stores successful calculations on the backend. The calculations table contains a record ID, expression, result, and creation time.

Database initialization is performed at server startup:

def init_db():
    with sqlite3.connect(DB_PATH) as db:
        db.execute(
            """
            CREATE TABLE IF NOT EXISTS calculations (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                expression TEXT NOT NULL,
                result REAL NOT NULL,
                created_at TEXT NOT NULL
            )
            """
        )

CREATE TABLE IF NOT EXISTS allows the server to initialize a new database without deleting records from an existing database.

The database file is created beside server.py. Its local development path is backend/calculator.db. In the documented Ubuntu deployment, its path is /opt/orbit-calculator/calculator.db.

The history endpoint reads records from SQLite:

with sqlite3.connect(DB_PATH) as db:
    db.row_factory = sqlite3.Row

    rows = db.execute(
        """
        SELECT id, expression, result, created_at
        FROM calculations
        ORDER BY id DESC
        """
    ).fetchall()

Records are returned in descending ID order so that newer calculations appear first. The backend converts the rows into JSON objects and returns them in a data array.

If the table is empty, the endpoint returns HTTP 200 with {"data":[]}. An empty history list is a successful response, not an error.

Single-record deletion uses the record ID:

with sqlite3.connect(DB_PATH) as db:
    cursor = db.execute(
        "DELETE FROM calculations WHERE id = ?",
        (record_id,),
    )

The placeholder keeps the ID separate from SQL syntax. The handler checks cursor.rowcount to determine whether a record was deleted. It returns HTTP 200 when deletion succeeds and HTTP 404 when the specified record does not exist.

Clearing all history uses:

with sqlite3.connect(DB_PATH) as db:
    db.execute("DELETE FROM calculations")

Successful database transactions are committed when their connection context exits normally.

After a successful deletion or clear-all request, Android calls GET /api/history again. It renders the returned list instead of treating a locally modified list as the authoritative database state.

11. Android Installation and Testing

11.1 Run with Android Studio

 Clone or download the Android frontend repository:

https://github.com/3785107480/EE308FZ_Yihan_Wang_android_frontend

Open Android Studio and select Open. Choose the repository root containing settings.gradle rather than selecting only the app directory.

Allow Gradle synchronization to complete and install Android SDK Platform 35 if requested. Initial dependency downloads require internet access.

Open Device Manager and create an emulator, such as Pixel 7 with Android API 35. On an Apple Silicon Mac, select an ARM64 system image.

Start the emulator, select the app run configuration, choose the running emulator, and click Run. After the application opens, confirm that the startup status displays “API connected”.

The backend URL is configured through the backendUrl property in the frontend repository’s root gradle.properties file:

backendUrl=http://8.217.14.75/api

The Android Gradle configuration reads this property and generates BuildConfig.API_BASE_URL. MainActivity reads the generated value instead of defining the server address directly in Java source code.

The base URL should end with /api without a trailing slash.

After changing backendUrl, rebuild and reinstall the application. The URL is included in the application during compilation; editing gradle.properties does not update an already installed APK.

11.2 Build and Install a Debug APK

Open the Android Studio Terminal at the frontend repository root.

On macOS or Linux, run:

./gradlew assembleDebug

In Windows PowerShell, run:

.\gradlew.bat assembleDebug

After a successful build, the APK is generated at:

adb install -r app/build/outputs/apk/debug/app-debug.apk

To install it on an emulator, drag the APK onto the running emulator. Alternatively, with Android platform tools available and a device connected, run:

adb install -r app/build/outputs/apk/debug/app-debug.apk

A debug APK is also available from the release page:

https://github.com/3785107480/EE308FZ_Yihan_Wang_android_frontend/releases/tag/v1.0.0

Open the release’s Assets section and download the APK file rather than the source-code archive.

For a physical Android device, transfer or download the APK and open it. If Android requests installation permission, allow installation from the browser or file manager used to open the file. The exact setting name depends on the Android version.

The application requires Android 6.0, API 23, or later. The debug APK is intended for assignment demonstration and evaluation.

The public configuration uses:

http://8.217.14.75/api

The device therefore needs network access to the public backend. After installation, open the application and check the startup connection status.

If the application displays “API offline”, open the health endpoint in a browser:

http://8.217.14.75/api/health

The expected response is:

{"status":"ok"}

The connection indicator is checked when the activity starts. It is not a continuously refreshed service monitor.

11.3 Local Backend Testing

Clone the backend repository separately:

https://github.com/3785107480/EE308FZ_Yihan_Wang_backend

Use Python 3.10 or later, as documented in the backend README. The service uses the Python standard library and does not require a separate database server.

From the backend repository root, run:

python3 backend/server.py --host 127.0.0.1 --port 8000

The server initializes its SQLite database automatically. Check the local service at:

http://127.0.0.1:8000/api/health

When testing with an Android Emulator, set the following property in the frontend repository’s root gradle.properties file:

backendUrl=http://10.0.2.2:8000/api

The Android Emulator uses 10.0.2.2 to access the host computer’s local service. Rebuild and reinstall the Android application after changing this value.

For a physical Android device on the same local network, start the backend with:

python3 backend/server.py --host 0.0.0.0 --port 8000

Find the computer’s LAN IP address and use it in backendUrl. For example, if the computer’s actual address is 192.168.1.100, configure:

backendUrl=http://192.168.1.100:8000/api

This address is an example and must be replaced with the computer’s real address. The computer’s firewall must allow the device to reach TCP port 8000.

Rebuild and reinstall the application after changing the address. Do not configure a physical phone to use localhost for a backend running on another computer, because localhost on the phone refers to the phone itself.

Before producing the public evaluation APK again, restore the public backendUrl value and rebuild.

12. Backend Deployment

 The backend is deployed on an Ubuntu 22.04 server using Python, SQLite, Nginx, and systemd.

The deployment keeps the Python service bound to 127.0.0.1:8000. Nginx accepts public HTTP requests on port 80 and forwards requests under /api/ to the Python service.

The request flow is:

Android application → Public server port 80 → Nginx reverse proxy → Python API on 127.0.0.1:8000 → SQLite database

The deployment files are provided in the backend repository’s deploy directory.

The server script is placed at:

/opt/orbit-calculator/server.py

The corresponding SQLite database is created at:

/opt/orbit-calculator/calculator.db

The systemd configuration is installed as:

/etc/systemd/system/orbit-calculator.service

The Nginx configuration is installed as:

/etc/nginx/sites-available/orbit-calculator

The internal service and directory names retain “orbit-calculator” for compatibility with the existing deployment. The visible application name is Yihan Wang Calculator.

The service account must have permission to write to the database directory. When updating the server, the existing calculator.db file must be preserved if its history is to be retained.

The systemd service starts the backend automatically and is configured to restart it after failure. Nginx provides the public entry point, and the cloud security group must allow inbound TCP port 80.

The deployment can be checked using the public health endpoint:

http://8.217.14.75/api/health

The expected response is:

{"status":"ok"}

History can be inspected through:

http://8.217.14.75/api/history

The current demonstration uses HTTP and shared history without user accounts. Evaluators accessing the same backend see the same history, so deletion and clear-all operations affect that shared dataset.

The backend must remain accessible throughout the assignment evaluation period. A successful health check confirms availability at the time of the request; it does not guarantee future availability.

13. Testing Results

The project uses parser unit tests, HTTP integration tests, and manual Android testing. These verify different aspects of the system and should not be treated as interchangeable evidence.

To run the backend test suite, open a terminal at the backend repository root and execute:

python3 -m unittest discover -s tests -v

Parser tests directly evaluate expressions and check parsing exceptions. HTTP integration tests start a local test server and use a temporary SQLite database to check responses and persistence without modifying the public deployment database.

During the available verification run, the parser tests passed. The HTTP integration tests could not complete because the restricted execution environment denied local socket binding. This is an execution-environment limitation, but it means that the full HTTP suite cannot be reported as passed on the basis of that run.

The Android screenshots and public API responses provide separate manual evidence of the demonstrated behavior.

13.1 Parser Tests

The parser unit tests include the following expressions and expected results:

Expression

Expected Result

12+8

20

8-3*2

2

1+2*3

7

(1+2)*3

9

10/2+7

12

-5+8

3

+5

5

3*-2

-6

10/4

2.5

.5+1.25

1.75

These tests cover arithmetic, operator precedence, parentheses, unary positive and negative signs, decimal input, and fractional results.

The corresponding parser assertions passed in the verification run.

13.2 Invalid Input Tests

The parser unit tests check rejection of an empty expression and the following invalid expressions:

2**3
(1+2
1+
abc
2 3

At the parser level, these inputs raise ExpressionError. A parser exception is not itself an HTTP response.

The separate HTTP integration test contains requests for 8/0, 2**3, an empty expression, and (2+3. Its assertions check HTTP 400, an error field, and an empty history list after the rejected requests.

The HTTP test cases are not identical to the complete parser test list. Their execution was blocked by the local socket restriction described above.

The Android invalid-expression screenshot separately demonstrates that 2**3 produces a visible backend error message.

13.3 Division-by-Zero Test

The parser unit test checks:

8/(3-3)

The denominator evaluates to zero, and the parser raises ExpressionError. This test passed.

The manual Android demonstration uses:

8/0

The application displays:

Division by zero is not allowed.

The calculation handler maps this parsing exception to HTTP 400 with an error field. Since parsing fails before the INSERT statement, the calculation is not stored.

The result display resets to 0 after the failed request. This is a UI reset value and does not mean that division by zero produced a valid result.

13.4 Persistence Test

The manual persistence check submits successful calculations, closes and reopens the Android application, and checks the history after the client reconnects.

The history-after-restart screenshot shows the previously stored records in the reopened client. Android retrieves these records through GET /api/history, and the backend reads them from SQLite.

The automated HTTP test also includes a server-restart check using the same temporary database. It stops and recreates the test server, then verifies that the original records remain available. This automated check is implemented but was not completed in the restricted verification environment.

13.5 Deletion Test

The manual deletion demonstration begins with multiple successful calculation records. A selected record is deleted, and the updated list shows that the other record remains.

The client sends DELETE /api/history/{id}, where the ID identifies the selected database record. After a successful response, Android reloads the history from the backend.

The HTTP integration test includes assertions that the selected ID disappears from both the API response and the temporary SQLite database. It also checks that deleting the same ID again returns HTTP 404.

These automated assertions describe the implemented test coverage, not a completed HTTP test run in the restricted environment.

13.6 Clear-All Test

The manual clear-all demonstration begins with existing history. After the user selects Clear all, the application displays:

No calculations yet.

The client sends DELETE /api/history and then reloads the list using GET /api/history.

The HTTP integration test includes assertions that the returned history array is empty and the database record count is zero after clearing. The execution limitation stated at the beginning of this section also applies to these assertions.

For an additional manual persistence check, reopen the application after clearing and verify that the list remains empty, provided no other user has added records to the shared backend.

13.7 Front-End and Back-End Separation Test

The backend service was stopped while the Android application remained open.

The Android interface could still accept button input, but submitting a new expression did not produce a valid calculation result. This is consistent with the implementation: Android sends the expression to the backend and has no local expression-evaluation fallback.

The connection label may continue to show its earlier startup status if the backend stops after the activity has opened. The relevant evidence is that a new calculation request fails, not that the label must immediately change.

After this test, the backend service must be restarted and the health endpoint checked so that evaluation can continue.

14. Personal Journey: Problems Encountered and Solutions

Problem 1: Gradle Build Configuration

The Android build initially reported an error because buildConfigField was placed at the wrong level of the Gradle configuration.

The solution was to enable BuildConfig generation inside the android block:

buildFeatures {
    buildConfig = true
}

The buildConfigField declaration was placed inside defaultConfig. This allowed the configured backend URL to be accessed through BuildConfig.API_BASE_URL.

This problem helped me understand that Gradle settings must be placed in the correct configuration blocks.

Problem 2: HTTP Access from Android

The demonstration backend uses HTTP. Android normally restricts cleartext traffic for applications targeting modern Android versions.

The application manifest contains:

android:usesCleartextTraffic="true"

This allows the assignment application to communicate with the public HTTP endpoint. The setting applies to the application configuration; it is not automatically limited to debug builds.

HTTPS would be preferable for a production deployment.

Problem 3: Backend Connectivity

The Android client needed a reachable backend address. A service running only on the computer’s localhost address is not directly accessible from a physical phone.

The public deployment uses a Python service bound to 127.0.0.1:8000 behind Nginx. Nginx exposes the API on port 80, and the Android backendUrl property points to the public /api address.

For local emulator testing, 10.0.2.2 provides access to the host computer. A physical device instead requires a reachable LAN or public address.

This clarified the difference between the emulator, the host computer, and the deployed server.

Problem 4: Expression Parsing

Executing expressions as general-purpose program code would violate the assignment requirements and introduce security risks.

The solution was a recursive-descent parser with explicit tokenization, operator precedence, parentheses, unary signs, decimal numbers, and error handling.

Separating expression, term, and factor made the evaluation order understandable and allowed invalid syntax to be rejected without executing user input.

Problem 5: Persistent History

A history list held only in Android memory would not provide backend persistence.

The solution was to store successful calculations in SQLite and retrieve them through GET /api/history. The client also reloads history after deletion so that its display follows the backend’s current data.

This showed why the database should be the authoritative history store rather than the Android view.

Problem 6: Two-Repository Submission

The assignment requires separate frontend and backend repositories.

The final project was divided into:

  • EE308FZ_Yihan_Wang_android_frontend
  • EE308FZ_Yihan_Wang_backend

The frontend repository contains the Android Gradle project, Java source code, configuration, README, and coding standards.

The backend repository contains the Python service, deployment files, tests, README, coding standards, and supporting documentation.

The two projects communicate through HTTP APIs and do not require a shared source directory.

15. Personal Learnings

 

This assignment improved my understanding of how to divide responsibilities between a frontend client and a backend service.

For this calculator, Android handles input, presentation, and communication. The backend owns expression validation, calculation, exception handling, and persistent history. Sending only the expression makes that boundary explicit.

I learned how HTTP methods and JSON responses form an API contract. POST submits a calculation, GET retrieves history, and DELETE removes records. The client must interpret the response according to both the endpoint and the HTTP status.

The parser helped me understand how recursive methods implement precedence, parentheses, and unary signs. Restricting input to a mathematical grammar is different from executing an expression as arbitrary Python code.

SQLite demonstrated how data can survive client restarts. Parameterized SQL also showed how values should be kept separate from SQL syntax.

Android development introduced practical issues involving Gradle configuration, background networking, UI-thread updates, cleartext HTTP, and emulator addressing.

Deployment introduced Nginx reverse proxy configuration, systemd service management, database file permissions, and public network access.

Finally, testing taught me to distinguish implementation, expected behavior, and observed results. A test that cannot run because of an environment restriction should not be reported as passed.

16. Future Improvements

The current project focuses on the required calculator functions. Future improvements would address security, usability, maintainability, and deployment reliability.

Security improvements include HTTPS, authentication, independent user histories, and request rate limiting. The current shared history model is suitable for a demonstration but does not provide user isolation.

Calculator improvements could include scientific functions, history search, pagination, theme switching, and keyboard input.

Usability improvements could include a persistent error display instead of relying only on short Toast messages. A history-loading failure should also be distinguished from a genuinely empty history list. A refreshable connection indicator would provide more current information than the startup-only health check.

Code improvements could separate HTTP handlers, parser logic, and database operations into different modules. More explicit connection cleanup and consistent server-side error handling would improve robustness.

Distribution improvements could include a release-signed APK and automated build verification. A CI pipeline could run both parser and HTTP integration tests in an environment that supports local test servers.

For a larger deployment, a production-oriented Python server stack, database backups, and deployment automation would be appropriate.

17. Conclusion

Yihan Wang Calculator implements an Android client and an independent Python backend communicating through HTTP and JSON.

The Android application accepts expressions, sends requests, displays backend results and errors, and presents calculation history. It does not calculate the final expression result locally.

The backend validates and parses expressions, performs calculations, rejects invalid input and division by zero, stores successful calculations in SQLite, retrieves history, and processes individual and clear-all deletion requests.

The blog documents the architecture, API contract, database design, implementation, installation, deployment, and available test evidence. Parser tests passed, and the screenshots demonstrate the manually tested application behavior. HTTP integration tests are included in the repository, with their verification limitation explicitly stated.

Android frontend repository:

https://github.com/3785107480/EE308FZ_Yihan_Wang_android_frontend

Python backend repository:

https://github.com/3785107480/EE308FZ_Yihan_Wang_backend

Android APK:

https://github.com/3785107480/EE308FZ_Yihan_Wang_android_frontend/releases/tag/v1.0.0

Public backend health check:

http://8.217.14.75/api/health

The backend must remain accessible during evaluation so that the installed Android application can calculate new expressions and retrieve persistent history.

...全文
366 回复 打赏 收藏 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
内容概要:本文研究了基于深度强化学习的多无人机辅助边缘计算网络路径规划方法,旨在通过深度强化学习算法优化多无人机在执行任务时的飞行路径,以提高边缘计算网络的服务质量和效率。文中系统阐述了深度强化学习的基本原理及其在无人机路径规划中的应用,重点解决了动态环境适应性和多目标优化等关键技术难题。研究提出了一种高效的算法框架,能够有效应对复杂环境中的障碍物规避、能耗最小化和任务完成时间最短化等多重挑战。该框架通过Matlab代码实现,并经过大量仿真实验验证,结果表明,相较于传统路径规划方法,所提方法在确保飞行安全的前提下,显著提升了无人机的任务执行效率和网络服务质量,展现了优异的性能表现和广阔的应用前景。; 适合人群:具备一定编程基础,特别是熟悉Matlab编程语言,对无人机技术、边缘计算或深度强化学习感兴趣的科研人员和技术开发者。; 使用场景及目标:①为研究人员提供一种新的无人机路径规划解决方案,特别是在复杂动态环境下;②促进深度强化学习技术在无人机辅助边缘计算网络中的应用和发展;③为相关领域的工程技术人员提供技术支持和参考案例。; 阅读建议:读者在阅读本文时,应重点关注深度强化学习算法的设计思路及其在无人机路径规划中的具体应用,同时结合提供的Matlab代码进行实践操作,以便更好地理解和掌握该技术的核心要点。
已经博主授权,源码转载自 https://pan.quark.cn/s/a4b39357ea24 卷积神经网络(CNN,Convolutional Neural Network)属于一种深度学习模型类别,它对于处理具有规则网格状结构的数据类型表现尤为出色,尤其是图像数据。在手写体识别这一具体任务中,CNN能够高效地识别图像中的关键特征,例如笔画的几何形态、走向以及相互连接的模式,进而精确地识别出特定的手写字符。本项目的实现依托于TensorFlow框架,这是一个由Google研发的开源机器学习平台,其提供了大量便捷的工具和应用程序接口(API),用于构建和训练结构复杂的神经网络模型。 我们需要深入掌握卷积神经网络的基本构造。CNN通常由多个核心组件构成,包括卷积层(Convolutional Layer)、池化层(Pooling Layer)、全连接层(Fully Connected Layer)以及非线性激活函数(例如ReLU)。卷积层借助滤波器(Filter)在输入图像上执行扫描操作,负责特征的提取工作;池化层则通过降低数据的空间维度来提升计算效率;全连接层将提取出的特征与输出类别进行关联映射,最后采用Softmax函数完成最终的分类任务。 在这个手写字符识别的项目实践中,初始阶段可能进行了数据预处理步骤,涉及读取特定的二进制文件(比如train-labels.idx1-ubyte、t10k-labels.idx1-ubyte、train-images.idx3-ubyte、t10k-images.idx3-ubyte),这些文件可能存储了MNIST数据集中的手写数字图像及其对应的类别标签。MNIST数据集作为机器学习领域广泛应用的标准化基准,包含了60,000个训练...

91

社区成员

发帖
与我相关
我的任务
社区描述
梅努斯-软件工程
软件工程 高校 福建省·福州市
社区管理员
  • FZU_SE_LQF
  • zjcTander
  • 木村修
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧