diff --git a/.bitrise-with-ui-tests.yml b/.bitrise-with-ui-tests.yml deleted file mode 100644 index 01df91a..0000000 --- a/.bitrise-with-ui-tests.yml +++ /dev/null @@ -1,48 +0,0 @@ ---- -format_version: 1.3.0 -default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git -trigger_map: -- push_branch: "*" - workflow: primary -- pull_request_source_branch: "*" - workflow: primary -workflows: - primary: - steps: - - activate-ssh-key@4.0.3: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@4.0.12: {} - - install-missing-android-tools@2.2.0: {} - - gradle-runner@1.8.3: - title: Gradle Build/Test/Analyse/Distribute - inputs: - - gradle_task: assembleDebug testDebug jacocoTestReport checkstyle pmd jdepend - lintDebug buildDashboard assembleDebugAndroidTest crashlyticsUploadDistributionDebug - -PversionCode=$BITRISE_BUILD_NUMBER -PfabricApiKey=$FABRIC_API_KEY -PfabricApiSecret=$FABRIC_API_SECRET - - mapping_file_exclude_filter: '' - - virtual-device-testing-for-android@1.0.3: - inputs: - - test_type: instrumentation - - gradle-coveralls@1.0.1: - inputs: - - coveralls_task: coveralls -PversionCode=$BITRISE_BUILD_NUMBER -PfabricApiKey=$FABRIC_API_KEY - -PfabricApiSecret=$FABRIC_API_SECRET - - coveralls_repo_token: "$COVERALLS_REPO_TOKEN" - - deploy-to-bitrise-io@1.3.18: - title: Deploy test reports to Bitrise.io - inputs: - - deploy_path: "$BITRISE_SOURCE_DIR/app/build/reports/jacocoTestReport/" - - notify_email_list: '' - - deploy-to-bitrise-io@1.3.18: - title: Deploy APK to Bitrise.io -app: - envs: - - opts: - is_expand: false - GRADLE_BUILD_FILE_PATH: build.gradle - - opts: - is_expand: false - GRADLE_TASK: assembleDebug - - opts: - is_expand: false - GRADLEW_PATH: "./gradlew" diff --git a/.bitrise.yml b/.bitrise.yml index ca284ad..768a0ef 100644 --- a/.bitrise.yml +++ b/.bitrise.yml @@ -1,59 +1,114 @@ --- -format_version: 1.3.0 +format_version: '13' default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git +project_type: android +meta: + bitrise.io: + stack: linux-docker-android-22.04 + machine_type_id: standard +pipelines: + pipelineBuildTestDistribute: + stages: + - stageBuildTestDistribute: {} +stages: + stageBuildTestDistribute: + workflows: + - build: {} + - androidTest: {} trigger_map: -- push_branch: "*" - workflow: primary -- pull_request_source_branch: "*" - workflow: primary + - push_branch: "*" + pipeline: pipelineBuildTestDistribute workflows: - primary: + androidTest: steps: - - activate-ssh-key@4.0.3: - run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - - git-clone@4.0.12: {} - - install-missing-android-tools@2.2.0: {} - - script@1.1.5: - inputs:Switched to shell script from Gradle runner at bitrise (#36) - - content: |- - #!/usr/bin/env bash - # fail if any commands fails - set -e - # debug log - set -x - - unset ANDROID_NDK_HOME - - ./gradlew assembleDebug testDebug jacocoTestReport checkstyle pmd jdepend lintDebug buildDashboard assembleDebugAndroidTest crashlyticsUploadDistributionDebug -PversionCode=$BITRISE_BUILD_NUMBER -PfabricApiKey=$FABRIC_API_KEY -PfabricApiSecret=$FABRIC_API_SECRET - title: Gradle Build/Test/Analyse/Distribute -# disabled because of https://github.com/vgaidarji/ci-matters/issues/35 -# - gradle-runner@1.8.3: -# title: Gradle Build/Test/Analyse/Distribute -# inputs: -# - gradle_task: assembleDebug testDebug jacocoTestReport checkstyle pmd jdepend -# lintDebug buildDashboard crashlyticsUploadDistributionDebug -PversionCode=$BITRISE_BUILD_NUMBER -# -PfabricApiKey=$FABRIC_API_KEY -PfabricApiSecret=$FABRIC_API_SECRET -# - mapping_file_exclude_filter: '' - - gradle-coveralls@1.0.1: - inputs: - - coveralls_task: coveralls -PversionCode=$BITRISE_BUILD_NUMBER -PfabricApiKey=$FABRIC_API_KEY - -PfabricApiSecret=$FABRIC_API_SECRET - - coveralls_repo_token: "$COVERALLS_REPO_TOKEN" - - deploy-to-bitrise-io@1.3.18: - title: Deploy test reports to Bitrise.io - inputs: - - deploy_path: "$BITRISE_SOURCE_DIR/app/build/reports/jacocoTestReport/" - - notify_email_list: '' - - deploy-to-bitrise-io@1.3.18: - title: Deploy APK to Bitrise.io + - activate-ssh-key@4.1: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@8.2: {} + - set-java-version@1: + inputs: + - set_java_version: '17' + - install-missing-android-tools@3.2: {} + - android-build-for-ui-testing@0: + inputs: + - variant: "$BUILD_TYPE" + - module: app + - avd-manager@1: + inputs: + - tag: default + - abi: x86_64 + - api_level: '34' + - wait-for-android-emulator@1: {} + - android-instrumented-test@0: {} + - deploy-to-bitrise-io@2.7: + title: Deploy build outputs to Bitrise.io + inputs: + - deploy_path: "$BITRISE_SOURCE_DIR/app/build/outputs" + - notify_email_list: '' + - deploy-to-bitrise-io@2.7: + title: Deploy test reports to Bitrise.io + inputs: + - deploy_path: "$BITRISE_SOURCE_DIR/app/build/reports" + - notify_email_list: '' + build: + steps: + - activate-ssh-key@4.1: + run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' + - git-clone@8.2: {} + - set-java-version@1: + inputs: + - set_java_version: '17' + - install-missing-android-tools@3.2: {} + - android-build@1: + inputs: + - variant: "$BUILD_TYPE" + - script@1.2: + inputs: + - content: |- + #!/usr/bin/env bash + # fail if any commands fails + set -e + # debug log + set -x + + ./gradlew testDebug jacocoTestReport lintDebug buildDashboard + title: Check + - script@1: + inputs: + - content: |- + #!/usr/bin/env bash + # fail if any commands fails + set -e + # make pipelines' return status equal the last command to exit with a non-zero status, or zero if all commands exit successfully + set -o pipefail + # debug log + set -x + + echo $FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON > $BITRISE_SOURCE_DIR/credentials.json + export FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON=$BITRISE_SOURCE_DIR/credentials.json && ./gradlew appDistributionUploadDebug + title: Distribute To Firebase + - codecov@3: + inputs: + - OS: linux + - CODECOV_TOKEN: "$CODECOV_TOKEN" + - deploy-to-bitrise-io@2.7: + title: Deploy test reports to Bitrise.io + inputs: + - deploy_path: "$BITRISE_SOURCE_DIR/app/build/reports/jacocoTestReport/" + - notify_email_list: '' + - custom-test-results-export@1: + inputs: + - search_pattern: "*/build/test-results/testDebugUnitTest/*" + - test_name: tests + - deploy-to-bitrise-io@2.7: + title: Deploy APK to Bitrise.io app: envs: - - opts: - is_expand: false - GRADLE_BUILD_FILE_PATH: build.gradle - - opts: - is_expand: false - GRADLE_TASK: assembleDebug - - opts: - is_expand: false - GRADLEW_PATH: "./gradlew" + - opts: + is_expand: false + GRADLE_BUILD_FILE_PATH: build.gradle + - opts: + is_expand: false + BUILD_TYPE: debug + - opts: + is_expand: false + GRADLEW_PATH: "./gradlew" \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index f18ffaf..5321194 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,56 +1,60 @@ -version: 2 +version: 2.1 +orbs: + android: circleci/android@2.5.0 + codecov: codecov/codecov@4.0.1 jobs: build: working_directory: ~/code - docker: - - image: circleci/android:api-28-alpha - environment: - JVM_OPTS: -Xmx3200m - ADB_INSTALL_TIMEOUT: 10 - TERM: dumb + executor: + name: android/android-machine + tag: 2024.01.1 steps: - checkout - - restore_cache: - key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} + - android/change-java-version: + java-version: 17 + - android/accept-licenses - run: - name: Accept Android Licenses - command: yes | sdkmanager --licenses && yes | sdkmanager --update || exit 0 + name: Build + command: ./gradlew assembleDebug - run: - name: Download Dependencies - command: ./gradlew clean androidDependencies - - save_cache: - paths: - - ~/.gradle - key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} + name: Check + command: ./gradlew testDebug jacocoTestReport lintDebug buildDashboard - run: - name: Install Android dependencies + name: Distribute command: | - sdkmanager "platform-tools" - sdkmanager "platforms;android-15" - sdkmanager "platforms;android-21" - sdkmanager "platforms;android-22" - sdkmanager "platforms;android-25" - sdkmanager "build-tools;24.0.3" - sdkmanager "extras;android;m2repository" - sdkmanager "extras;google;m2repository" - sdkmanager "extras;google;google_play_services" - sdkmanager "system-images;android-21;default;armeabi-v7a" - - run: - name: Setup emulator - command: echo "no" | avdmanager create avd -n test -k "system-images;android-21;default;armeabi-v7a" - - run: - name: Launch emulator - command: export LD_LIBRARY_PATH=${ANDROID_HOME}/emulator/lib64:${ANDROID_HOME}/emulator/lib64/qt/lib && emulator -avd test -noaudio -no-boot-anim -no-window -accel on - background: true - - run: - name: Wait for emulator - command: circle-android wait-for-boot - - run: - name: Run UI tests - command: ./gradlew connectedAndroidTest - - run: - name: Build and check - command: ./gradlew testDebug jacocoTestReport coveralls checkstyle pmd jdepend lintDebug buildDashboard crashlyticsUploadDistributionDebug -PpreDexEnable=false -PversionCode=$CIRCLE_BUILD_NUM -PfabricApiKey=$FABRIC_API_KEY -PfabricApiSecret=$FABRIC_API_SECRET + echo $FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON_B64 | base64 -d | tee $(pwd)/google-services-account.json > /dev/null + export FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON=$(pwd)/google-services-account.json && ./gradlew appDistributionUploadDebug + - codecov/upload + - store_artifacts: + path: app/build/outputs + destination: outputs + - store_artifacts: + path: app/build/reports + destination: reports + - store_test_results: + path: app/build/test-results + androidTest: + working_directory: ~/code + executor: + name: android/android-machine + tag: 2024.01.1 + steps: + - checkout + - android/change-java-version: + java-version: 17 + - android/accept-licenses + - android/create-avd: + avd-name: test + install: true + system-image: system-images;android-32;default;x86_64 + - android/start-emulator: + avd-name: test + no-window: true + restore-gradle-cache-prefix: v1a + - android/run-tests: + test-command: ./gradlew connectedAndroidTest --stacktrace + - android/save-gradle-cache: + cache-prefix: v1a - store_artifacts: path: app/build/outputs destination: outputs @@ -59,3 +63,9 @@ jobs: destination: reports - store_test_results: path: app/build/test-results + +workflows: + build_and_test: + jobs: + - build + - androidTest \ No newline at end of file diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 6675935..41c66fc 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -4,45 +4,51 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Build - uses: vgaidarji/android-github-actions-build@v1.0.1 - env: - FABRIC_API_KEY: ${{ secrets.FABRIC_API_KEY }} - FABRIC_API_SECRET: ${{ secrets.FABRIC_API_SECRET }} + - uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v3 with: - args: "sdkmanager 'platforms;android-26' && ./gradlew assembleDebug -PpreDexEnable=false" + distribution: 'zulu' + java-version: 17 + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + - name: Build + run: "./gradlew assembleDebug" - name: Check - uses: vgaidarji/android-github-actions-build@v1.0.1 - env: - FABRIC_API_KEY: ${{ secrets.FABRIC_API_KEY }} - FABRIC_API_SECRET: ${{ secrets.FABRIC_API_SECRET }} - with: - args: "./gradlew testDebug jacocoTestReport checkstyle pmd jdepend lintDebug buildDashboard -PpreDexEnable=false" + run: "./gradlew testDebug jacocoTestReport lintDebug buildDashboard" - name: Distribute - uses: vgaidarji/android-github-actions-build@v1.0.1 - env: - FABRIC_API_KEY: ${{ secrets.FABRIC_API_KEY }} - FABRIC_API_SECRET: ${{ secrets.FABRIC_API_SECRET }} + run: | + echo '${{ secrets.FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON }}' > $RUNNER_TEMP/credentials.json + export FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON=$RUNNER_TEMP/credentials.json && ./gradlew appDistributionUploadDebug + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4.0.1 with: - args: "./gradlew crashlyticsUploadDistributionDebug -PpreDexEnable=false" - - name: Publish Code Coverage - uses: vgaidarji/android-github-actions-build@v1.0.1 - env: - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} - with: - args: "./gradlew coveralls -PpreDexEnable=false" + token: ${{ secrets.CODECOV_TOKEN }} + slug: vgaidarji/ci-matters androidTest: - runs-on: macOS-latest + runs-on: ubuntu-latest + strategy: + matrix: + api-level: [ 34 ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: 17 + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - name: Gradle cache + uses: gradle/actions/setup-gradle@v3 - name: Instrumentation Tests uses: reactivecircus/android-emulator-runner@v2 - env: - FABRIC_API_KEY: ${{ secrets.FABRIC_API_KEY }} - FABRIC_API_SECRET: ${{ secrets.FABRIC_API_SECRET }} with: - api-level: 29 - arch: x86 + api-level: ${{ matrix.api-level }} + arch: x86_64 + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true script: ./gradlew connectedAndroidTest --stacktrace diff --git a/GITHUB_ACTIONS.md b/GITHUB_ACTIONS.md index 19e092e..c3d8898 100644 --- a/GITHUB_ACTIONS.md +++ b/GITHUB_ACTIONS.md @@ -17,7 +17,7 @@ GitHub provides [basic documentation](https://developer.github.com/actions) on h See these projects' `README` and `Dockerfile` files for configuration details. Use `vgaidarji/android-github-actions/build@v1.0.0` for regular build steps and provide commands via action `args` parameter. - For example: `args = "./gradlew assembleDebug -PpreDexEnable=false"`. This will be passed to `entrypoint.sh` of the appropriate GitHub Action and executed in root folder of the workspace. + For example: `args = "./gradlew assembleDebug"`. This will be passed to `entrypoint.sh` of the appropriate GitHub Action and executed in root folder of the workspace. Use `vgaidarji/android-github-actions/emulator@v1.0.0` for running UI tests on Android Emulator. By default, this action will execute [ui-test-on-emulator](https://github.com/vgaidarji/android-github-actions/blob/0381c333953e22b4b95d4ef843effefb35c67fdf/emulator/Dockerfile#L18) script (see [script sources](https://github.com/vgaidarji/docker-android/blob/570d8f3aacd6af72b817254d08c99cd5bae57636/docker-android-emulator/ui-tests-on-emulator.sh)). diff --git a/JENKINS.md b/JENKINS.md index fc344d9..450f6cb 100644 --- a/JENKINS.md +++ b/JENKINS.md @@ -68,7 +68,7 @@ In order to build the project we need to have environment variables set in Jenki In "Tasks" section we need to specify a list of Gradle tasks to be invoked. - For example: `clean connectedAndroidTest assembleDebug testDebug jacocoTestReport checkstyle pmd jdepend lintDebug buildDashboard -PversionCode=${BUILD_NUMBER} -PfabricApiKey="YOUR_KEY" -PfabricApiSecret="YOUR_SECRET"` + For example: `clean connectedAndroidTest assembleDebug testDebug jacocoTestReport lintDebug buildDashboard -PversionCode=${BUILD_NUMBER} -PfabricApiKey="YOUR_KEY" -PfabricApiSecret="YOUR_SECRET"` 6. Configure reports diff --git a/README.md b/README.md index e40ad98..09fa840 100644 --- a/README.md +++ b/README.md @@ -3,23 +3,26 @@ Integration (comparison) of different continuous integration services on Android ### CI's integration -* [x] [Jenkins](./JENKINS.md) -* [x] [Travis CI](./TRAVIS.md) [![Build Status](https://travis-ci.org/vgaidarji/ci-matters.svg?branch=master)](https://travis-ci.org/vgaidarji/ci-matters) +* [x] [GitHub Actions](./GITHUB_ACTIONS.md) ![Build Status](https://github.com/github/docs/actions/workflows/main.yml/badge.svg?branch=master) * [x] [Bitrise](./BITRISE.md) [![Build Status](https://app.bitrise.io/app/002b43ae8a42b6b1/status.svg?token=xT4EDBQWGNcSWJveU6IEVA&branch=master)](https://app.bitrise.io/app/002b43ae8a42b6b1) -* [x] [TeamCity](./TEAM_CITY.md) -* [x] [BuddyBuild](./BUDDY_BUILD.md) [![BuddyBuild](https://dashboard.buddybuild.com/api/statusImage?appID=58398ac5beb35b010082e315&branch=master&build=latest)](https://dashboard.buddybuild.com/apps/58398ac5beb35b010082e315/build/latest) -* [x] [Shippable](./SHIPPABLE.md) [![Run Status](https://api.shippable.com/projects/5832c72ab8b8e41000a5eb5c/badge?branch=master)](https://app.shippable.com/projects/5832c72ab8b8e41000a5eb5c) [![Coverage Badge](https://api.shippable.com/projects/5832c72ab8b8e41000a5eb5c/coverageBadge?branch=master)](https://app.shippable.com/projects/5832c72ab8b8e41000a5eb5c) * [x] [Circle](./CIRCLE.md) [![CircleCI](https://circleci.com/gh/vgaidarji/ci-matters.svg?style=svg)](https://circleci.com/gh/vgaidarji/ci-matters) -* [x] [GitHub Actions](./GITHUB_ACTIONS.md) +* [x] [Jenkins](./JENKINS.md) +* [x] [TeamCity](./TEAM_CITY.md) ### TODO -* [ ] Nevercode.io +* [ ] Codemagic.io * [ ] Gitlab CI * [ ] Drone.io --- +### Codecov.io + +[![codecov](https://codecov.io/gh/vgaidarji/ci-matters/graph/badge.svg?token=ubhWNTji7m)](https://codecov.io/gh/vgaidarji/ci-matters) + +`Codecov.io` provides test coverage information. `CODECOV_TOKEN` environment variable should be exported on the build machine. + ### Comparison #### Comparison table @@ -30,15 +33,12 @@ This table should help people make a decision which CI to choose for the project | ------------- |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| | Jenkins |:star:|:star:|:star:|:star:|:star:|:bust_in_silhouette::raised_hands:|:radio:/:computer:|:free:| | TeamCity |:star:|:star:|:star:|:star:|:star:|:bust_in_silhouette::raised_hands:/:cloud:|:computer:|:moneybag::moneybag::moneybag:| -| Travis CI |:star:|:star:|:star:|:star:|:x:|:cloud:|:computer:|:moneybag::moneybag:| | Bitrise |:star:|:star:|:star:|:star:|:x:|:cloud:|:computer:|:moneybag::moneybag:| -| Shippable |:star:|:star:|:star:|:star:|:x:|:cloud:|:radio:|:moneybag:| | Circle CI |:star:|:star:|:star:|:star:|:x:|:cloud:|:computer:|:moneybag::moneybag:| -| Buddybuild |:star:|:star:|:x:|:x:|:x:|:cloud:|:computer:|:moneybag::moneybag:| | GitHub Actions|:star:|:star:|:star:|:star:|:x:|:cloud:|:computer:|:free:| | Gitlab CI |.|.|.|.|.|.|.|:moneybag:| | Nevecode.io |.|.|.|.|.|.|.|:moneybag::moneybag:| -| Drone.io |.|.|.|.|.|.|.|:moneybag:| +| Codemagic.io |.|.|.|.|.|.|.|:moneybag:| |. |. |.|. |. |. |. |. | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -67,12 +67,10 @@ What might matter is the starting price for paid plan: * [Travis CI](https://travis-ci.com/plans) - starts with **69$/month.** (1 concurrent build, ∞ projects, ∞ build time) * [Bitrise](https://www.bitrise.io/pricing) - starts with **50$/month.** (1 concurrent build, ∞ projects, 45 min. max build time) * [TeamCity](https://www.jetbrains.com/teamcity/buy/#license-type=new-license) - starts with **299$.** (4 concurrent builds, 30 project configurations, ∞ build time) -* [Shippable](https://app.shippable.com/pricing.html) - starts with **25$/month.** (2 concurrent builds, ∞ projects, ∞ build time) * [Gitlab CI](https://about.gitlab.com/products/) - starts with **15$/month.** (2 concurrent builds, ∞ projects, ∞ build time) * [Circle CI](https://circleci.com/pricing/) - starts with **39$/month.** (2 concurrent builds, ∞ projects, 500 minutes build time per month) * [Nevercode.io](https://nevercode.io/pricing/) - starts with **5$/month.** (1 concurrent build, ∞ projects, 90 min. max build time) * [Drone.io](https://drone.io/pricing) - starts with **25$/month.** (1 concurrent build, 5 private projects, ∞ build time) -* [Buddybuild](https://www.buddybuild.com/pricing/) - starts with **79$/month.** (1 concurrent build, ∞ projects, ∞ build time) #### Presentation @@ -85,35 +83,24 @@ It doesn't contain information about ALL existing CI services, but should be a g In few words: - [Jenkins](https://jenkins.io/)/[TeamCity](https://www.jetbrains.com/teamcity/) for complex workflow -- [Travis CI](https://travis-ci.org/)/[Circle CI](https://circleci.com/) for open-source projects +- [Circle CI](https://circleci.com/) for open-source projects - [Bitrise.io](https://bitrise.io/) for any workflow --- -### Checkstyle - -Project uses custom Checkstyle [rules](https://github.com/vgaidarji/ci-matters/blob/master/app/config/checkstyle/checkstyle.xml). - ---- - -### Fabric/Crashlytics project configuration - -In order to upload APK to Crashlytics project should have following configuration: -`${projectDir}/fabric.properties` file with `apiSecret` and `io.fabric.ApiKey` in AndroidManifest.xml([1](https://github.com/vgaidarji/ci-matters/blob/master/app/src/main/AndroidManifest.xml#L17), -[2](https://github.com/vgaidarji/ci-matters/blob/master/app/build.gradle#L59)) file. -**Both keys should not be uploaded to the repository for security reasons!** - -Pass both parameters to your build from command line: - - ./gradlew -PfabricApiKey="YOUR_API_KEY" -PfabricApiSecret="YOUR_API_SECRET" crashlyticsUploadDistributionDebug +### Firebase App Distribution project configuration -or export these keys as environment variables on a build machine -and they will be automatically read from there during the build (no need to pass keys as parameters in this case). +In order to distribute Android builds to Firebase, Google Service Account ([link](https://firebase.google.com/docs/app-distribution/android/distribute-gradle#authenticate)) credentials should be created. +Service Account then needs to be saved in secure place and exported in `FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON={service-account.json}` environment variable on CI. +Use secret variables on CI/repo level for this. ------- +Distribute the build using following command: -### Coveralls + ./gradlew appDistributionUpload{Debug|Release} -[![Coverage Status](https://coveralls.io/repos/github/vgaidarji/ci-matters/badge.svg)](https://coveralls.io/github/vgaidarji/ci-matters) +### Archive -`Coveralls` provides test coverage information. `COVERALLS_REPO_TOKEN` environment variable should be exported on the build machine. +Following CIs are no longer integrated (either because they have been discontinued or due to pricing policy change such as Travis). +* [x] [Travis CI](archive/travis/TRAVIS.md) +* [x] [BuddyBuild](archive/buddybuild/BUDDY_BUILD.md) +* [x] [Shippable](archive/shippable/SHIPPABLE.md) diff --git a/app/build.gradle b/app/build.gradle index 607af29..d839c38 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,10 @@ apply plugin: 'com.android.application' -apply plugin: 'io.fabric' +apply plugin: 'org.jetbrains.kotlin.android' +apply plugin: 'com.google.gms.google-services' +apply plugin: 'com.google.firebase.appdistribution' +apply plugin: 'com.google.firebase.crashlytics' apply from: 'config/quality.gradle' apply from: 'config/jacoco.gradle' -apply from: 'config/coveralls.gradle' def versionMajor = 1 def versionMinor = 0 @@ -38,85 +40,59 @@ def getVersionName = { -> return name } -def getFabricApiKey = { -> - def noKey = "0000000000000000000000000000000000000000" - def key = project.hasProperty('fabricApiKey') ? fabricApiKey.toString() : - System.getenv("FABRIC_API_KEY") ?: noKey - if (key == noKey) - println "Fabric API key not found" - else - println "Fabric API key found" - return key -} - -def getFabricApiSecret = { -> - def secret = project.hasProperty('fabricApiSecret') ? fabricApiSecret.toString() : - System.getenv("FABRIC_API_SECRET") ?: "" - if (secret.isEmpty()) - println "Fabric API Secret not found" - else - println "Fabric API Secret found" - return secret -} - -task fabricProperties() { - description "Generate fabric.properties file with apiSecret" - ant.propertyfile(file: "$projectDir/fabric.properties") { - entry(key: "apiSecret", value: getFabricApiSecret()) - } -} - -preBuild.dependsOn('fabricProperties') - android { - compileSdkVersion 25 - buildToolsVersion "24.0.3" + compileSdk 34 defaultConfig { applicationId "com.vgaidarji.cimatters" - minSdkVersion 15 - targetSdkVersion 25 + minSdkVersion 24 + targetSdkVersion 34 versionCode getVersionCode() versionName getVersionName() - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - - manifestPlaceholders = [ - fabricApiKey: getFabricApiKey() - ] + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { debug { - testCoverageEnabled = true + applicationIdSuffix ".debug" + debuggable true + firebaseAppDistribution { + artifactType="APK" + serviceCredentialsFile = System.getenv('FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON') + } } release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + firebaseAppDistribution { + artifactType="APK" + serviceCredentialsFile = System.getenv('FIREBASE_APP_DISTRIBUTION_SERVICE_ACCOUNT_JSON') + } } } + buildFeatures { + viewBinding = true + } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } lintOptions { checkReleaseBuilds false abortOnError false } -} - -repositories { - maven { url 'https://maven.fabric.io/public' } + namespace 'com.vgaidarji.cimatters' } dependencies { - compile fileTree(include: ['*.jar'], dir: 'libs') - compile 'com.android.support:appcompat-v7:25.0.0' - compile 'com.jakewharton:butterknife:5.1.1' - compile 'com.android.support:design:25.0.0' - compile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') { - transitive = true; - } - testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-all:2.0.2-beta' - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { - exclude group: 'com.android.support', module: 'support-annotations' - }) + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.11.0' + implementation platform('com.google.firebase:firebase-bom:32.7.4') + implementation 'com.google.firebase:firebase-analytics' + implementation 'com.google.firebase:firebase-crashlytics' + implementation 'androidx.test.ext:junit-ktx:1.1.5' + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:5.11.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test:runner:1.5.2' + androidTestImplementation 'androidx.test:rules:1.5.0' } diff --git a/app/config/checkstyle/checkstyle-suppressions.xml b/app/config/checkstyle/checkstyle-suppressions.xml deleted file mode 100755 index 201feb0..0000000 --- a/app/config/checkstyle/checkstyle-suppressions.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/app/config/checkstyle/checkstyle.gradle b/app/config/checkstyle/checkstyle.gradle deleted file mode 100755 index 14b8a02..0000000 --- a/app/config/checkstyle/checkstyle.gradle +++ /dev/null @@ -1,54 +0,0 @@ -apply plugin: 'checkstyle' - -checkstyle { - toolVersion = '7.1.2' -} - -task checkstyleMain(type: Checkstyle) { - ignoreFailures = false - showViolations = true - - configProperties.checkstyleSuppressionsPath = - file("${project.rootDir}/app/config/checkstyle/checkstyle-suppressions.xml") - configFile file("${project.rootDir}/app/config/checkstyle/checkstyle.xml") - source 'src/main/java' - include '**/*.java' - exclude '**/gen/**' - exclude '**/R.java' - exclude '**/BuildConfig.java' - reports { - xml.destination "$project.buildDir/reports/checkstyle/main.xml" - } - // empty classpath - classpath = files() -} - -task checkstyleTest(type: Checkstyle) { - ignoreFailures = false - showViolations = true - - configProperties.checkstyleSuppressionsPath = - file("${project.rootDir}/app/config/checkstyle/checkstyle-suppressions.xml") - configFile file("${project.rootDir}/app/config/checkstyle/checkstyle.xml") - source 'src/test/java', 'src/androidTest/java' - include '**/*.java' - exclude '**/gen/**' - exclude '**/R.java' - exclude '**/BuildConfig.java' - reports { - xml.destination "$project.buildDir/reports/checkstyle/test.xml" - } - // empty classpath - classpath = files() -} - -task checkstyle(dependsOn: ['checkstyleMain', 'checkstyleTest']) { - description 'Runs Checkstyle inspection against Android sourcesets.' - group = 'Code Quality' -} - -dependencies { - checkstyle('com.puppycrawl.tools:checkstyle:7.1.2') -} - -preBuild.dependsOn('checkstyle') diff --git a/app/config/checkstyle/checkstyle.xml b/app/config/checkstyle/checkstyle.xml deleted file mode 100755 index dabcc3e..0000000 --- a/app/config/checkstyle/checkstyle.xml +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/config/coveralls.gradle b/app/config/coveralls.gradle deleted file mode 100644 index 70488c3..0000000 --- a/app/config/coveralls.gradle +++ /dev/null @@ -1,6 +0,0 @@ -apply plugin: 'com.github.kt3k.coveralls' - -coveralls { - sourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs).files.absolutePath - jacocoReportPath = "${buildDir}/reports/jacocoTestReport/jacocoTestReport.xml" -} diff --git a/app/config/jacoco.gradle b/app/config/jacoco.gradle index e9fcbaa..dec16a6 100644 --- a/app/config/jacoco.gradle +++ b/app/config/jacoco.gradle @@ -5,15 +5,15 @@ ext { } jacoco { - toolVersion = "0.7.5.201505241946" - reportsDir = file("$buildDir/reports") + toolVersion = "0.8.11" + reportsDirectory = file("${buildDir}/reports") } task jacocoTestReport(type:JacocoReport, dependsOn: "testDebugUnitTest") { group = "Reporting" description = "Generate Jacoco coverage reports for Debug build" classDirectories = fileTree( - dir: "$buildDir/intermediates/classes/debug", + dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: ['**/R.class', '**/R$*.class', '**/*$ViewBinder*.*', @@ -30,12 +30,12 @@ task jacocoTestReport(type:JacocoReport, dependsOn: "testDebugUnitTest") { println '##teamcity[jacocoReport dataPath=\'app/build/jacoco/testDebugUnitTest.exec\' includes=\'com.vgaidarji.cimatters.*\' excludes=\'com.vgaidarji.cimatters.test.* **/*R*.* **/*Injector*.* **/*Activity*.* .*R .*CiMattersApplication .*BuildConfig .*Activity .*Test \']' } reports { - xml.enabled = true - html.enabled = true + xml.required = true + html.required = true } additionalSourceDirs = files(coverageSourceDirs) sourceDirectories = files(coverageSourceDirs) - executionData = files("$buildDir/jacoco/testDebugUnitTest.exec") + executionData = files("${buildDir}/jacoco/testDebugUnitTest.exec") } tasks.withType(Test) { diff --git a/app/config/pmd/pmd-ruleset.xml b/app/config/pmd/pmd-ruleset.xml deleted file mode 100755 index 1647c89..0000000 --- a/app/config/pmd/pmd-ruleset.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - Custom ruleset for Android application - - .*/R.java - .*/gen/.* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/config/quality.gradle b/app/config/quality.gradle index 602ff33..1ed7ba7 100644 --- a/app/config/quality.gradle +++ b/app/config/quality.gradle @@ -1,61 +1,14 @@ -apply plugin: 'pmd' apply plugin: 'build-dashboard' -apply plugin: 'jdepend' -apply from: 'config/checkstyle/checkstyle.gradle' android { lintOptions { abortOnError false xmlReport true htmlReport true - lintConfig file("${project.rootDir}/app/config/lint/lint.xml") - htmlOutput file("$project.buildDir/reports/lint/lint-result.html") - xmlOutput file("$project.buildDir/reports/lint/lint-result.xml") + lintConfig file("${buildDir}/app/config/lint/lint.xml") + htmlOutput file("${buildDir}/reports/lint/lint-result.html") + xmlOutput file("${buildDir}/reports/lint/lint-result.xml") } } -task pmd(type: Pmd) { - description 'Run PMD/CPD analysis.' - group 'verification' - - ignoreFailures = true - ruleSetFiles = files("${project.rootDir}/app/config/pmd/pmd-ruleset.xml") - ruleSets = [] - - source = fileTree('src/main/java') - include '**/*.java' - exclude '**/gen/**' - - reports { - xml.enabled = true - html.enabled = true - xml { - destination "$project.buildDir/reports/pmd/pmd.xml" - } - html { - destination "$project.buildDir/reports/pmd/pmd.html" - } - } - - doLast { - File outDir = new File("$project.buildDir/reports/pmd/") - ant.taskdef(name: 'cpd', classname: 'net.sourceforge.pmd.cpd.CPDTask', - classpath: configurations.pmd.asPath) - ant.cpd(minimumTokenCount: '50', format: 'xml', - outputFile: new File(outDir, 'cpd.xml')) { - fileset(dir: "src/main/java") { - include(name: '**/*.java') - } - } - } -} - -task(jdepend, type: JDepend) { - group = "Verification" - description 'Run JDepend plugin' - classesDir = file('build/intermediates/classes/debug/') - reports { - xml.enabled true - text.enabled false - } -} +// TODO: configure detekt \ No newline at end of file diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..497bc52 --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,48 @@ +{ + "project_info": { + "project_number": "3225927432", + "project_id": "ci-matters-a498c", + "storage_bucket": "ci-matters-a498c.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:3225927432:android:1299f0babc1137e8b2b6bd", + "android_client_info": { + "package_name": "com.vgaidarji.cimatters" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyC-6cjWLUnfo7hd0dXRW_Gd2E_CxUrXOEk" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:3225927432:android:10d44974fc042c1eb2b6bd", + "android_client_info": { + "package_name": "com.vgaidarji.cimatters.debug" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyC-6cjWLUnfo7hd0dXRW_Gd2E_CxUrXOEk" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/vgaidarji/cimatters/LoginActivityTest.java b/app/src/androidTest/java/com/vgaidarji/cimatters/LoginActivityTest.java deleted file mode 100644 index 4f3a4a0..0000000 --- a/app/src/androidTest/java/com/vgaidarji/cimatters/LoginActivityTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.vgaidarji.cimatters; - -import android.support.test.rule.ActivityTestRule; -import android.support.test.runner.AndroidJUnit4; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.action.ViewActions.typeText; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static org.hamcrest.CoreMatchers.allOf; - -@RunWith(AndroidJUnit4.class) -public class LoginActivityTest { - - @Rule - public ActivityTestRule activityTestRule = - new ActivityTestRule<>(LoginActivity.class); - - @Test - public void onLoginClick_shouldOpenNextActivityForAllowedCredentials() throws Exception { - onView(withId(R.id.edit_text_email)).perform(typeText("test@test.com")); - onView(withId(R.id.edit_text_password)).perform(typeText("1111")); - onView(withId(R.id.button_login)).perform(click()); - - onView(withText("OK")).check(matches(isDisplayed())); - } - - @Test - public void onLoginClick_shouldShowErrorForIncorrectCredentials() throws Exception { - onView(withId(R.id.edit_text_email)).perform(typeText("wrong@email.com")); - onView(withId(R.id.edit_text_password)).perform(typeText("not_a_password")); - onView(withId(R.id.button_login)).perform(click()); - - onView(allOf(withId(android.support.design.R.id.snackbar_text), - withText("Wrong credentials."))) - .check(matches(isDisplayed())); - } -} diff --git a/app/src/androidTest/java/com/vgaidarji/cimatters/LoginActivityTest.kt b/app/src/androidTest/java/com/vgaidarji/cimatters/LoginActivityTest.kt new file mode 100644 index 0000000..3c4db51 --- /dev/null +++ b/app/src/androidTest/java/com/vgaidarji/cimatters/LoginActivityTest.kt @@ -0,0 +1,50 @@ +package com.vgaidarji.cimatters + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.hamcrest.CoreMatchers +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class LoginActivityTest { + @get:Rule + val activityTestRule = ActivityScenarioRule( + LoginActivity::class.java + ) + + @Test + @Throws(Exception::class) + fun onLoginClick_shouldOpenNextActivityForAllowedCredentials() { + onView(withId(R.id.edit_text_email)) + .perform(ViewActions.typeText("test@test.com")) + onView(withId(R.id.edit_text_password)) + .perform(ViewActions.typeText("1111")) + onView(withId(R.id.button_login)).perform(ViewActions.click()) + onView(withText("OK")) + .check(ViewAssertions.matches(isDisplayed())) + } + + @Test + @Throws(Exception::class) + fun onLoginClick_shouldShowErrorForIncorrectCredentials() { + onView(withId(R.id.edit_text_email)) + .perform(ViewActions.typeText("wrong@email.com")) + onView(withId(R.id.edit_text_password)) + .perform(ViewActions.typeText("not_a_password")) + onView(withId(R.id.button_login)).perform(ViewActions.click()) + onView( + CoreMatchers.allOf( + withId(R.id.snackbar_text), // Update this ID based on your layout + withText("Wrong credentials.") + ) + ).check(ViewAssertions.matches(isDisplayed())) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4b3da13..03841db 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + @@ -12,11 +11,8 @@ android:theme="@style/AppTheme" android:name=".CiMattersApplication"> - - - + diff --git a/app/src/main/java/com/vgaidarji/cimatters/CiMattersApplication.java b/app/src/main/java/com/vgaidarji/cimatters/CiMattersApplication.java deleted file mode 100644 index 644e1ce..0000000 --- a/app/src/main/java/com/vgaidarji/cimatters/CiMattersApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.vgaidarji.cimatters; - -import android.app.Application; -import com.crashlytics.android.Crashlytics; -import io.fabric.sdk.android.Fabric; - -public class CiMattersApplication extends Application { - @Override - public void onCreate() { - super.onCreate(); - Fabric.with(this, new Crashlytics()); - } -} diff --git a/app/src/main/java/com/vgaidarji/cimatters/LoginActivity.java b/app/src/main/java/com/vgaidarji/cimatters/LoginActivity.java deleted file mode 100644 index 2b9a844..0000000 --- a/app/src/main/java/com/vgaidarji/cimatters/LoginActivity.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.vgaidarji.cimatters; - -import android.content.Intent; -import android.os.Bundle; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.Snackbar; -import android.support.v7.app.AppCompatActivity; -import android.widget.Button; -import android.widget.EditText; -import butterknife.ButterKnife; -import butterknife.InjectView; -import butterknife.OnClick; - -public class LoginActivity extends AppCompatActivity implements LoginView { - - @InjectView(R.id.coordinator) - CoordinatorLayout coordinatorLayout; - @InjectView(R.id.edit_text_email) - EditText editTextEmail; - @InjectView(R.id.edit_text_password) - EditText editTextPassword; - @InjectView(R.id.button_login) - Button buttonLogin; - - LoginPresenter presenter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_login); - setTitle(getString(R.string.activity_login)); - ButterKnife.inject(this); - presenter = new LoginPresenter(this); - } - - @OnClick(R.id.button_login) - public void onLoginClick() { - presenter.onLoginClick( - editTextEmail.getText().toString(), - editTextPassword.getText().toString() - ); - } - - @Override - public void openNextActivity() { - startActivity(new Intent(LoginActivity.this, MainActivity.class)); - finish(); - } - - @Override - public void showError(String message) { - Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG).show(); - } -} diff --git a/app/src/main/java/com/vgaidarji/cimatters/LoginPresenter.java b/app/src/main/java/com/vgaidarji/cimatters/LoginPresenter.java deleted file mode 100644 index ec0f611..0000000 --- a/app/src/main/java/com/vgaidarji/cimatters/LoginPresenter.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.vgaidarji.cimatters; - -class LoginPresenter { - - private static final String EMAIL = "test@test.com"; - private static final String PASSWORD = "1111"; - private static final String WRONG_CREDENTIALS = "Wrong credentials."; - private final LoginView view; - - LoginPresenter(LoginView view) { - this.view = view; - } - - void onLoginClick(String email, String password) { - if (email.equals(EMAIL) && password.equals(PASSWORD)) { - view.openNextActivity(); - } else { - view.showError(WRONG_CREDENTIALS); - } - } -} diff --git a/app/src/main/java/com/vgaidarji/cimatters/LoginView.java b/app/src/main/java/com/vgaidarji/cimatters/LoginView.java deleted file mode 100644 index 45aaa7b..0000000 --- a/app/src/main/java/com/vgaidarji/cimatters/LoginView.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.vgaidarji.cimatters; - -interface LoginView { - void openNextActivity(); - - void showError(String message); -} diff --git a/app/src/main/java/com/vgaidarji/cimatters/MainActivity.java b/app/src/main/java/com/vgaidarji/cimatters/MainActivity.java deleted file mode 100644 index 3bf374c..0000000 --- a/app/src/main/java/com/vgaidarji/cimatters/MainActivity.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.vgaidarji.cimatters; - -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; - -public class MainActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - setTitle(getString(R.string.activity_main)); - } -} diff --git a/app/src/main/kotlin/com/vgaidarji/cimatters/CiMattersApplication.kt b/app/src/main/kotlin/com/vgaidarji/cimatters/CiMattersApplication.kt new file mode 100644 index 0000000..b874caf --- /dev/null +++ b/app/src/main/kotlin/com/vgaidarji/cimatters/CiMattersApplication.kt @@ -0,0 +1,9 @@ +package com.vgaidarji.cimatters + +import android.app.Application + +class CiMattersApplication : Application() { + override fun onCreate() { + super.onCreate() + } +} diff --git a/app/src/main/kotlin/com/vgaidarji/cimatters/LoginActivity.kt b/app/src/main/kotlin/com/vgaidarji/cimatters/LoginActivity.kt new file mode 100644 index 0000000..e6a5a03 --- /dev/null +++ b/app/src/main/kotlin/com/vgaidarji/cimatters/LoginActivity.kt @@ -0,0 +1,39 @@ +package com.vgaidarji.cimatters + +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.snackbar.Snackbar +import com.vgaidarji.cimatters.databinding.ActivityLoginBinding + +class LoginActivity : AppCompatActivity(), LoginView { + + private lateinit var binding: ActivityLoginBinding + private lateinit var presenter: LoginPresenter + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityLoginBinding.inflate(layoutInflater) + setContentView(binding.root) + title = getString(R.string.activity_login) + presenter = LoginPresenter(this) + setupLoginButtonClickListener() + } + + private fun setupLoginButtonClickListener() { + binding.buttonLogin.setOnClickListener { + presenter.onLoginClick( + binding.editTextEmail.text.toString(), + binding.editTextPassword.text.toString() + ) + } + } + + override fun openNextActivity() { + startActivity(Intent(this@LoginActivity, MainActivity::class.java)) + finish() + } + + override fun showError(message: String?) { + message?.let { Snackbar.make(binding.coordinator, it, Snackbar.LENGTH_LONG).show() } + } +} diff --git a/app/src/main/kotlin/com/vgaidarji/cimatters/LoginPresenter.kt b/app/src/main/kotlin/com/vgaidarji/cimatters/LoginPresenter.kt new file mode 100644 index 0000000..dd22df2 --- /dev/null +++ b/app/src/main/kotlin/com/vgaidarji/cimatters/LoginPresenter.kt @@ -0,0 +1,17 @@ +package com.vgaidarji.cimatters + +internal class LoginPresenter(private val view: LoginView) { + fun onLoginClick(email: String, password: String) { + if (email == EMAIL && password == PASSWORD) { + view.openNextActivity() + } else { + view.showError(WRONG_CREDENTIALS) + } + } + + companion object { + private const val EMAIL = "test@test.com" + private const val PASSWORD = "1111" + private const val WRONG_CREDENTIALS = "Wrong credentials." + } +} diff --git a/app/src/main/kotlin/com/vgaidarji/cimatters/LoginView.kt b/app/src/main/kotlin/com/vgaidarji/cimatters/LoginView.kt new file mode 100644 index 0000000..60d182c --- /dev/null +++ b/app/src/main/kotlin/com/vgaidarji/cimatters/LoginView.kt @@ -0,0 +1,6 @@ +package com.vgaidarji.cimatters + +internal interface LoginView { + fun openNextActivity() + fun showError(message: String?) +} diff --git a/app/src/main/kotlin/com/vgaidarji/cimatters/MainActivity.kt b/app/src/main/kotlin/com/vgaidarji/cimatters/MainActivity.kt new file mode 100644 index 0000000..f749e84 --- /dev/null +++ b/app/src/main/kotlin/com/vgaidarji/cimatters/MainActivity.kt @@ -0,0 +1,12 @@ +package com.vgaidarji.cimatters + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + title = getString(R.string.activity_main) + } +} diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 924f905..0415496 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -1,5 +1,5 @@ - + android:autofillHints="" /> + android:autofillHints="" />