├── .gitignore
├── .idea
├── .gitignore
├── androidTestResultsUserPreferences.xml
└── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── example
│ │ └── androidconcepts
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── androidconcepts
│ │ │ ├── MainActivity.kt
│ │ │ ├── advancedConcurrency
│ │ │ ├── fileDownloader
│ │ │ │ ├── DownloadedFile.kt
│ │ │ │ ├── File.kt
│ │ │ │ ├── FileDownloadUseCase.kt
│ │ │ │ └── FileDownloader.kt
│ │ │ └── producerConsumer
│ │ │ │ ├── ProducerConsumerServer1.java
│ │ │ │ ├── example1
│ │ │ │ └── WrongProducerConsumerExample1.java
│ │ │ │ ├── example2
│ │ │ │ └── CorrectProducerConsumerExample2.java
│ │ │ │ ├── example3
│ │ │ │ └── BQProducerConsumerExample3.java
│ │ │ │ ├── example4
│ │ │ │ ├── BQDishRackExample4.java
│ │ │ │ └── Dish.java
│ │ │ │ └── example5
│ │ │ │ └── BQDishRackExample5.java
│ │ │ ├── cancellation
│ │ │ ├── coroutines
│ │ │ │ └── learning1
│ │ │ │ │ ├── CoroutineCancelActivity.kt
│ │ │ │ │ ├── PerformMoneyTransactionUseCase.kt
│ │ │ │ │ └── learning1.md
│ │ │ ├── rxjava
│ │ │ │ └── learning1
│ │ │ │ │ ├── PerformMoneyTransactionRxUseCase.kt
│ │ │ │ │ ├── RxCancelActivity.kt
│ │ │ │ │ └── learning1.md
│ │ │ └── threads
│ │ │ │ └── ThreadInterruption1UseCase.kt
│ │ │ ├── common
│ │ │ ├── BaseObservable.kt
│ │ │ ├── BgThreadPoster.kt
│ │ │ ├── EmitOnRegisterObservable.kt
│ │ │ └── UiThreadPoster.kt
│ │ │ ├── coroutines
│ │ │ ├── learning1
│ │ │ │ ├── CoroutineLearning1Controller.kt
│ │ │ │ ├── FibonacciUseCases.kt
│ │ │ │ └── Learning1.md
│ │ │ └── learning2
│ │ │ │ ├── CoroutineLearning2Activity.kt
│ │ │ │ └── WhileTrueLoopRunner.kt
│ │ │ ├── jcip
│ │ │ ├── chapter2_threadSafety
│ │ │ │ ├── atomicSyncBug
│ │ │ │ │ ├── CorrectAtomicServer.java
│ │ │ │ │ └── WrongAtomicServer.java
│ │ │ │ ├── intrinsicLocks
│ │ │ │ │ ├── HomeWidget.java
│ │ │ │ │ ├── Widget.java
│ │ │ │ │ └── learning.md
│ │ │ │ ├── sync
│ │ │ │ │ └── SyncServer.java
│ │ │ │ └── syncPerfIssue
│ │ │ │ │ ├── FastServer.java
│ │ │ │ │ └── SlowServer.java
│ │ │ ├── chapter3_sharingObjects
│ │ │ │ ├── escape
│ │ │ │ │ └── WrongPublishAndEscapeExample1.java
│ │ │ │ └── visibility
│ │ │ │ │ ├── CorrectVisibilityExample3.java
│ │ │ │ │ ├── ForwardSubstitutionExample.java
│ │ │ │ │ ├── Learning1.md
│ │ │ │ │ ├── NoVisibilityExample.java
│ │ │ │ │ └── NoVisibilityExample2.java
│ │ │ ├── common
│ │ │ │ ├── DelayedCallbackUseCase.java
│ │ │ │ └── ThreadContextSwitchTrigger.java
│ │ │ └── extras
│ │ │ │ └── AndroidThreadSpammer.kt
│ │ │ ├── jcr
│ │ │ └── collections
│ │ │ │ ├── Learning.md
│ │ │ │ ├── collectionsInterface
│ │ │ │ ├── CollectionInterfaceKt.kt
│ │ │ │ └── CollectionsInterfaceExample1.java
│ │ │ │ ├── listInterface
│ │ │ │ └── ListInterfaceExample1.java
│ │ │ │ └── setInterface
│ │ │ │ └── SetInterfaceExample1.java
│ │ │ ├── lifecycle
│ │ │ ├── common
│ │ │ │ ├── FragmentConfigChangeImpl.kt
│ │ │ │ └── LifecycleLoggerFragment.kt
│ │ │ └── manualRecreation
│ │ │ │ ├── learning1
│ │ │ │ ├── ManualConfig1Activity.kt
│ │ │ │ └── ManualConfig1Fragment.kt
│ │ │ │ └── learning2
│ │ │ │ ├── ManualConfig2Fragment.kt
│ │ │ │ ├── ManualConfig2ViewMvc.kt
│ │ │ │ ├── ManualConfig2ViewMvcLandscapeImpl.kt
│ │ │ │ ├── ManualConfig2ViewMvcPortraitImpl.kt
│ │ │ │ └── ManualConfigActivity2.kt
│ │ │ ├── livedata
│ │ │ ├── learning1
│ │ │ │ ├── Learning1.md
│ │ │ │ ├── LiveDataApiEndpointSync.kt
│ │ │ │ ├── after
│ │ │ │ │ ├── LiveDataLearning1AfterController.kt
│ │ │ │ │ └── LiveDataLearning1AfterUseCase.kt
│ │ │ │ └── before
│ │ │ │ │ ├── LiveDataLearning1BeforeController.kt
│ │ │ │ │ └── LiveDataLearning1BeforeUseCase.kt
│ │ │ └── learning2
│ │ │ │ ├── ODHController.kt
│ │ │ │ ├── ODHUsecase.kt
│ │ │ │ └── ObservableDataHolder.kt
│ │ │ ├── ood
│ │ │ ├── fsm
│ │ │ │ └── login
│ │ │ │ │ ├── LoginScreenController.kt
│ │ │ │ │ ├── LoginViewMvc.kt
│ │ │ │ │ └── UserManager.kt
│ │ │ └── solid
│ │ │ │ └── o
│ │ │ │ └── learning1
│ │ │ │ ├── Employee.kt
│ │ │ │ ├── Learning1.md
│ │ │ │ ├── after
│ │ │ │ ├── SalaryCalculatorAfter.kt
│ │ │ │ ├── TaxCalculator.kt
│ │ │ │ └── TaxCalculatorFactory.kt
│ │ │ │ └── before
│ │ │ │ └── SalaryCalculatorBefore.kt
│ │ │ ├── rxJava
│ │ │ └── ownImpl
│ │ │ │ ├── ReactObservable.kt
│ │ │ │ └── ReactObservableUsage1.kt
│ │ │ └── ui
│ │ │ └── retroDesign
│ │ │ ├── RetroLayout.kt
│ │ │ ├── RetroLowElevationLayout.kt
│ │ │ └── RetroShimmerView.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── border.xml
│ │ ├── ic_baseline_arrow_circle_right_24.xml
│ │ ├── ic_baseline_star_24.xml
│ │ └── ic_launcher_background.xml
│ │ ├── layout-land
│ │ ├── activity_manual_config1.xml
│ │ ├── activity_manual_config2.xml
│ │ ├── fragment_manual_config_1.xml
│ │ └── fragment_manual_config_2.xml
│ │ ├── layout
│ │ ├── activity_coroutine_cancel.xml
│ │ ├── activity_coroutine_learning2.xml
│ │ ├── activity_main.xml
│ │ ├── activity_manual_config1.xml
│ │ ├── activity_manual_config2.xml
│ │ ├── activity_rx_cancel.xml
│ │ ├── fragment_manual_config_1.xml
│ │ ├── fragment_manual_config_2.xml
│ │ ├── layout_retro_default.xml
│ │ └── layout_retro_low_elev.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── androidconcepts
│ ├── advancedConcurrency
│ ├── fileDownloader
│ │ ├── FileDownloadUseCaseTest.kt
│ │ └── FileDownloaderTD.kt
│ └── producerConsumer
│ │ └── ProducerConsumerTest.java
│ ├── common
│ ├── Benchmark.kt
│ ├── BgThreadPosterTest.kt
│ └── UiThreadPosterTD.kt
│ ├── coroutines
│ ├── learning1
│ │ ├── FibonacciUseCaseCallbackTest.kt
│ │ └── FibonacciUseCaseUsingCoroutineTest.kt
│ └── performance
│ │ ├── AddImageFiltersUseCase.kt
│ │ ├── CoroutinePerformanceTest.kt
│ │ └── ProcessImageUseCase.kt
│ ├── jcip
│ ├── chapter2_threadSafety
│ │ ├── atomicSyncBug
│ │ │ └── AtomicServerTest.java
│ │ ├── intrinsicLocks
│ │ │ └── HomeWidgetTest.java
│ │ ├── sync
│ │ │ └── SyncServerTest.java
│ │ └── syncPerfIssue
│ │ │ └── ServerTest.java
│ └── chapter3_sharingObjects
│ │ ├── escape
│ │ └── EscapeExampleTest.java
│ │ └── visibility
│ │ └── VisibilityExampleTest.java
│ ├── jcr
│ └── collections
│ │ ├── CollectionPerformanceTest.kt
│ │ ├── CollectionsInterfaceTest.kt
│ │ ├── ListInterfaceTest.kt
│ │ └── SetInterfaceTest.kt
│ ├── livedata
│ └── learning2
│ │ └── ObservableDataHolderTest.kt
│ └── rxJava
│ └── ownImpl
│ └── ReactObservableUsage1Test.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 | .idea/gradle.xml
17 | .idea/compiler.xml
18 | .idea/misc.xml
19 | .idea/workspace.xml
20 | .idea/vcs.xml
21 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/androidTestResultsUserPreferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
89 |
90 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | xmlns:android
18 |
19 | ^$
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | xmlns:.*
29 |
30 | ^$
31 |
32 |
33 | BY_NAME
34 |
35 |
36 |
37 |
38 |
39 |
40 | .*:id
41 |
42 | http://schemas.android.com/apk/res/android
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | .*:name
52 |
53 | http://schemas.android.com/apk/res/android
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | name
63 |
64 | ^$
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | style
74 |
75 | ^$
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | .*
85 |
86 | ^$
87 |
88 |
89 | BY_NAME
90 |
91 |
92 |
93 |
94 |
95 |
96 | .*
97 |
98 | http://schemas.android.com/apk/res/android
99 |
100 |
101 | ANDROID_ATTRIBUTE_ORDER
102 |
103 |
104 |
105 |
106 |
107 |
108 | .*
109 |
110 | .*
111 |
112 |
113 | BY_NAME
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android Concepts : Learn By Doing!
2 |
3 | My personal learning topic-wise project with inDepth concepts and comments/learning summary. (See unit-tests too)
4 | Readme is in progress.
5 |
6 | ## Topics covered [see package structure/prs/branches if not found] :
7 |
8 | ### [File Downloader : LLD + advanced concurrency](https://github.com/sidsharma2002/AndroidConcepts/tree/main/app/src/main/java/com/example/androidconcepts/advancedConcurrency/fileDownloader)
9 |
10 | The use of cache and synchronization on downloading names in this FileDownloadUseCase class is to ensure that the same file is not downloaded multiple times concurrently.The cachedFiles HashMap is used to store previously downloaded files, and the currentlyDownloadingFiles list is used to keep track of files that are currently being downloaded. Before starting a new download, the code checks if the file is already in the cache or if it is being downloaded by another thread. If it is already in the cache, the cached file is returned; if it is being downloaded by another thread, the code waits until the download is complete before starting a new download. This is achieved by using synchronized blocks and wait() and notifyAll() methods on a monitor object.
11 |
12 | By doing this, the code ensures that only one thread downloads a file at a time and that files are not downloaded multiple times unnecessarily, which saves time and resources.
13 | See Unit-Tests too.
14 |
15 | ### [Producer Consumer : advanced concurrency](https://github.com/sidsharma2002/AndroidConcepts/tree/main/app/src/main/java/com/example/androidconcepts/advancedConcurrency/producerConsumer)
16 |
17 | This is an example of a Producer-Consumer pattern, which is a common design pattern in concurrent programming, Demonstrates how multiple threads can work together in a synchronized manner to complete a complex task, and how the Producer-Consumer pattern can be used to manage shared resources (in this case, dishes) between these threads.
18 |
19 | ### [Cancellations in Coroutines and RxJava : proper reasoning of why coroutines is hard](https://github.com/sidsharma2002/AndroidConcepts/tree/main/app/src/main/java/com/example/androidconcepts/cancellation)
20 | ### [General Observables](https://github.com/sidsharma2002/AndroidConcepts/blob/main/app/src/main/java/com/example/androidconcepts/common/BaseObservable.kt)
21 | ### [Background thread executors : replacement for simple background work](https://github.com/sidsharma2002/AndroidConcepts/blob/main/app/src/main/java/com/example/androidconcepts/common/BgThreadPoster.kt)
22 |
23 | The BgThreadPoster class is useful in situations where you need to perform a time-consuming task in the background, such as loading data from a remote server, without blocking the main thread. Less overengineered than coroutines.
24 |
25 | ### [Coroutines : benefits + how even coroutines can block and not suspend](https://github.com/sidsharma2002/AndroidConcepts/tree/main/app/src/main/java/com/example/androidconcepts/coroutines)
26 | ### [Java concurrency in practice learnings : advanced concurrency, not for kids](https://github.com/sidsharma2002/AndroidConcepts/tree/main/app/src/main/java/com/example/androidconcepts/jcip)
27 | ### [Livedata : benefits](https://github.com/sidsharma2002/AndroidConcepts/tree/main/app/src/main/java/com/example/androidconcepts/livedata/learning1)
28 | ### [How to create your own livedata better than google's livedata](https://github.com/sidsharma2002/AndroidConcepts/blob/main/app/src/main/java/com/example/androidconcepts/livedata/learning2/ObservableDataHolder.kt)
29 | ### [Threads doesn't interrupt immediately](https://github.com/sidsharma2002/AndroidConcepts/blob/main/app/src/main/java/com/example/androidconcepts/cancellation/threads/ThreadInterruption1UseCase.kt)
30 | ### [Retro UI ,uncomment activity_main layout file](https://github.com/sidsharma2002/AndroidConcepts/tree/main/app/src/main/java/com/example/androidconcepts/ui/retroDesign)
31 | https://user-images.githubusercontent.com/53833109/233896079-566c138d-a3b4-4e39-9b30-a77326829124.mp4
32 |
33 | #### Released :
34 | 
35 |
36 | #### Pressed :
37 | 
38 |
39 | #### Clicked + Shimmer Animation :
40 | 
41 |
42 | ### [Manually handling config changes and not let the activity destroy, Along with strategy-pattern applied on view logic.](https://github.com/sidsharma2002/AndroidConcepts/tree/main/app/src/main/java/com/example/androidconcepts/lifecycle)
43 | ### [Java complete reference : data structure operations with measured time, intresting facts.](https://github.com/sidsharma2002/AndroidConcepts/tree/main/app/src/main/java/com/example/androidconcepts/jcr/collections)
44 |
45 | It creates several large collections (ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap) and measures the time taken to perform various operations like adding and removing elements, iterating over elements, etc. The test is meant to compare the performance of different collections under different scenarios and help developers choose the appropriate collection type for their specific use case.
46 | [See its unit-test here](https://github.com/sidsharma2002/AndroidConcepts/blob/main/app/src/test/java/com/example/androidconcepts/jcr/collections/CollectionPerformanceTest.kt)
47 |
48 | ### [Spamming threads : starting 100s of threads and busting myth that threads are heavy.](https://github.com/sidsharma2002/AndroidConcepts/blob/main/app/src/main/java/com/example/androidconcepts/jcip/extras/AndroidThreadSpammer.kt)
49 | ### [Creating own RxJava like framework](https://github.com/sidsharma2002/AndroidConcepts/tree/main/app/src/main/java/com/example/androidconcepts/rxJava/ownImpl)
50 | ### Problems with fragment and its navigation [wip]
51 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'com.example.androidconcepts'
8 | compileSdk 31
9 |
10 | defaultConfig {
11 | applicationId "com.example.androidconcepts"
12 | minSdk 21
13 | targetSdk 31
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = '1.8'
32 | }
33 | }
34 |
35 | dependencies {
36 |
37 | implementation 'androidx.core:core-ktx:1.7.0'
38 | implementation 'androidx.appcompat:appcompat:1.4.1' // 1.5.1 gives error
39 | implementation 'com.google.android.material:material:1.6.0' // 1.7.0 gives error
40 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
41 |
42 | // Coroutines
43 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
44 |
45 | // Rx-Java
46 | implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
47 | // Because RxAndroid releases are few and far between, it is recommended you also
48 | // explicitly depend on RxJava's latest version for bug fixes and new features.
49 | // (see https://github.com/ReactiveX/RxJava/releases for latest 3.x.x version)
50 | implementation 'io.reactivex.rxjava3:rxjava:3.1.5'
51 |
52 | testImplementation "io.mockk:mockk:1.12.3"
53 | testImplementation 'junit:junit:4.13.2'
54 | testImplementation 'junit:junit:4.12'
55 | androidTestImplementation 'androidx.test.ext:junit:1.1.4'
56 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
57 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/androidconcepts/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.example.androidconcepts", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
19 |
23 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
36 |
39 |
40 |
43 |
46 |
47 |
48 |
51 |
54 |
55 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
67 |
68 |
71 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 |
6 | class MainActivity : AppCompatActivity() {
7 |
8 | override fun onCreate(savedInstanceState: Bundle?) {
9 | super.onCreate(savedInstanceState)
10 | setContentView(R.layout.activity_main)
11 |
12 | // AndroidThreadSpammer().execute()
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/advancedConcurrency/fileDownloader/DownloadedFile.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.fileDownloader
2 |
3 | data class DownloadedFile(
4 | val name: String,
5 | val url: String,
6 | val data: List
7 | )
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/advancedConcurrency/fileDownloader/File.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.fileDownloader
2 |
3 | data class File(
4 | val name: String,
5 | val url: String
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/advancedConcurrency/fileDownloader/FileDownloadUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.fileDownloader
2 |
3 | import androidx.annotation.WorkerThread
4 | import com.example.androidconcepts.common.BgThreadPoster
5 | import com.example.androidconcepts.common.UiThreadPoster
6 | import java.util.concurrent.Future
7 |
8 | class FileDownloadUseCase constructor(
9 | private val bgThreadPoster: BgThreadPoster = BgThreadPoster(),
10 | private val uiThreadPoster: UiThreadPoster = UiThreadPoster(),
11 | private val fileDownloader: FileDownloader = FileDownloaderImpl()
12 | ) {
13 | interface Listener {
14 | fun onFileResult(downloadedFile: DownloadedFile)
15 | }
16 |
17 | private val cachedFiles: HashMap =
18 | hashMapOf() // concurrent hashmap doesn't work here for some reason but synchronized does.
19 |
20 | private val MONITOR = Any()
21 |
22 | private val currentlyDownloadingFiles = mutableListOf()
23 |
24 | fun startDownloadFileAsync(file: File, listener: Listener): Future<*> = bgThreadPoster.post {
25 | waitIfSameFileIsDownloading(file.name)
26 |
27 | currentlyDownloadingFiles.add(file.name)
28 | val downloadedFile = getFromCacheOrDownloadFile(file)
29 | currentlyDownloadingFiles.remove(file.name)
30 |
31 | notifyAllMonitors()
32 |
33 | if (downloadedFile != null)
34 | uiThreadPoster.post {
35 | listener.onFileResult(downloadedFile)
36 | }
37 | }
38 |
39 | @WorkerThread
40 | private fun getFromCacheOrDownloadFile(file: File): DownloadedFile? {
41 | if (cachedFiles.contains(file.name)) {
42 | return cachedFiles[file.name]!!
43 | }
44 |
45 | val downloadedFile = fileDownloader.downloadFileSync(file)
46 |
47 | if (downloadedFile.downloadedFile != null) { // is Result.Success didn't work here!
48 | cachedFiles[file.name] = downloadedFile.downloadedFile
49 | return downloadedFile.downloadedFile
50 | }
51 |
52 | return null // error case
53 | }
54 |
55 | private fun notifyAllMonitors() {
56 | synchronized(MONITOR) {
57 | (MONITOR as Object).notifyAll()
58 | }
59 | }
60 |
61 | private fun waitIfSameFileIsDownloading(fileName: String) {
62 | synchronized(MONITOR) {
63 | while (currentlyDownloadingFiles.contains(fileName)) {
64 | (MONITOR as Object).wait()
65 | }
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/advancedConcurrency/fileDownloader/FileDownloader.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.fileDownloader
2 |
3 | import androidx.annotation.WorkerThread
4 | import com.example.androidconcepts.advancedConcurrency.fileDownloader.FileDownloader.*
5 |
6 | interface FileDownloader {
7 | @WorkerThread
8 | fun downloadFileSync(file: File): Result
9 |
10 | sealed class Result(val downloadedFile: DownloadedFile?, val errorMessage: String? = null) {
11 | class Success(downloadedFile: DownloadedFile) : Result(downloadedFile)
12 | class Error(errorMessage: String) : Result(null, errorMessage)
13 | }
14 | }
15 |
16 | class FileDownloaderImpl : FileDownloader {
17 | override fun downloadFileSync(file: File): Result {
18 | Thread.sleep(500L)
19 |
20 | return Result.Success(
21 | DownloadedFile(
22 | name = file.name,
23 | url = file.url,
24 | data = listOf()
25 | )
26 | )
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/advancedConcurrency/producerConsumer/ProducerConsumerServer1.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.producerConsumer;
2 |
3 | public class ProducerConsumerServer1 {
4 | private int hits = 0;
5 |
6 | public synchronized int incrementAndPut() {
7 | hits++;
8 | return hits;
9 | }
10 |
11 | public synchronized int get() {
12 | return hits;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/advancedConcurrency/producerConsumer/example1/WrongProducerConsumerExample1.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.producerConsumer.example1;
2 |
3 | import com.example.androidconcepts.advancedConcurrency.producerConsumer.ProducerConsumerServer1;
4 |
5 | public class WrongProducerConsumerExample1 {
6 |
7 | private final ProducerConsumerServer1 server = new ProducerConsumerServer1();
8 |
9 | private final Runnable producer = () -> {
10 | int hits = 0;
11 |
12 | while (true) {
13 | server.incrementAndPut();
14 | // to make it finite loop
15 | hits++;
16 | if (hits > 1000) {
17 | return;
18 | }
19 | }
20 | };
21 |
22 | private final Runnable consumer = () -> {
23 | int hits = 0;
24 |
25 | while (true) {
26 | server.get();
27 | // to make it finite loop
28 | hits++;
29 | if (hits > 1000) {
30 | return;
31 | }
32 | }
33 | };
34 |
35 | public void startExecution() throws InterruptedException {
36 | new Thread(producer).start();
37 | new Thread(consumer).start();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/advancedConcurrency/producerConsumer/example2/CorrectProducerConsumerExample2.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.producerConsumer.example2;
2 |
3 | import com.example.androidconcepts.advancedConcurrency.producerConsumer.ProducerConsumerServer1;
4 |
5 | public class CorrectProducerConsumerExample2 {
6 | private final ProducerConsumerServer1 server = new ProducerConsumerServer1();
7 |
8 | private boolean isValueUnconsumed = false;
9 |
10 | private final Runnable producer = () -> {
11 | int hits = 0;
12 |
13 | while (true) {
14 | waitTillValueIsConsumed();
15 |
16 | int newValue = server.incrementAndPut();
17 | System.out.println("put : " + newValue);
18 |
19 | markValueUnconsumedAndNotify();
20 |
21 | // to make it finite loop
22 | hits++;
23 | if (hits > 10000) {
24 | return;
25 | }
26 | }
27 | };
28 |
29 | private void waitTillValueIsConsumed() {
30 | synchronized (this) {
31 | while (isValueUnconsumed) {
32 | try {
33 | wait();
34 | } catch (InterruptedException e) {
35 | e.printStackTrace();
36 | }
37 | }
38 | }
39 | }
40 |
41 | private void markValueUnconsumedAndNotify() {
42 | synchronized (this) {
43 | isValueUnconsumed = true;
44 | notifyAll();
45 | }
46 | }
47 |
48 | private final Runnable consumer = () -> {
49 | while (true) {
50 | waitTillValueBecomesUnconsumed();
51 |
52 | int value = server.get();
53 | System.out.println("get : " + value);
54 |
55 | markValueConsumedAndNotify();
56 | }
57 | };
58 |
59 | private void waitTillValueBecomesUnconsumed() {
60 | synchronized (this) {
61 | while (!isValueUnconsumed) {
62 | try {
63 | wait();
64 | } catch (InterruptedException e) {
65 | e.printStackTrace();
66 | }
67 | }
68 | }
69 | }
70 |
71 | private void markValueConsumedAndNotify() {
72 | synchronized (this) {
73 | isValueUnconsumed = false;
74 | notify();
75 | }
76 | }
77 |
78 | public void startExecution() {
79 | new Thread(producer).start();
80 | new Thread(consumer).start();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/advancedConcurrency/producerConsumer/example3/BQProducerConsumerExample3.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.producerConsumer.example3;
2 |
3 | import com.example.androidconcepts.advancedConcurrency.producerConsumer.ProducerConsumerServer1;
4 |
5 | import java.util.concurrent.ArrayBlockingQueue;
6 | import java.util.concurrent.BlockingQueue;
7 |
8 | public class BQProducerConsumerExample3 {
9 | private final ProducerConsumerServer1 server = new ProducerConsumerServer1();
10 | private final BlockingQueue unConsumedValueBQ = new ArrayBlockingQueue<>(1);
11 |
12 | private final Runnable producer = () -> {
13 | int hits = 0;
14 |
15 | while (true) {
16 | int updatedValue = server.incrementAndPut();
17 |
18 | try {
19 | unConsumedValueBQ.put(updatedValue);
20 | System.out.println("put value = " + updatedValue);
21 | } catch (InterruptedException e) {
22 | e.printStackTrace();
23 | }
24 |
25 | // to make it finite loop
26 | hits++;
27 | if (hits > 10000) {
28 | return;
29 | }
30 | }
31 | };
32 |
33 | private final Runnable consumer = () -> {
34 | int hits = 0;
35 |
36 | while (true) {
37 | try {
38 | int value = unConsumedValueBQ.take();
39 | System.out.println("get value = " + value);
40 | } catch (InterruptedException e) {
41 | e.printStackTrace();
42 | }
43 |
44 | // to make it finite loop
45 | hits++;
46 | if (hits > 10000) {
47 | return;
48 | }
49 | }
50 | };
51 |
52 | public void startExecution() {
53 | new Thread(producer).start();
54 | new Thread(consumer).start();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/advancedConcurrency/producerConsumer/example4/BQDishRackExample4.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.producerConsumer.example4;
2 |
3 | import java.util.concurrent.ArrayBlockingQueue;
4 | import java.util.concurrent.BlockingQueue;
5 |
6 | public class BQDishRackExample4 {
7 |
8 | private final BlockingQueue dishRack = new ArrayBlockingQueue<>(3);
9 |
10 | private Runnable dishWasher = () -> {
11 | int id = 0;
12 |
13 | while (true) {
14 | Dish dishWashed = new Dish(++id);
15 |
16 | try {
17 | dishRack.put(dishWashed);
18 | System.out.println("dish washed : " + id);
19 | } catch (InterruptedException e) {
20 | e.printStackTrace();
21 | }
22 |
23 | // for finite loop
24 | if (id > 10000) {
25 | return;
26 | }
27 | }
28 | };
29 |
30 | private Runnable dishCleaner = () -> {
31 | while (true) {
32 | try {
33 | Dish dishToBeCleaned = dishRack.take();
34 | System.out.println("dish cleaned : " + dishToBeCleaned.getId());
35 | } catch (InterruptedException e) {
36 | e.printStackTrace();
37 | }
38 | }
39 | };
40 |
41 | public void startExecution() {
42 | new Thread(dishWasher).start();
43 | new Thread(dishCleaner).start();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/advancedConcurrency/producerConsumer/example4/Dish.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.producerConsumer.example4;
2 |
3 | public class Dish {
4 | private int id = 0;
5 |
6 | public Dish(int id) {
7 | this.id = id;
8 | }
9 |
10 | public synchronized int getId() {
11 | return id;
12 | }
13 |
14 | public synchronized void setId(int id) {
15 | this.id = id;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/advancedConcurrency/producerConsumer/example5/BQDishRackExample5.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.producerConsumer.example5;
2 |
3 | import com.example.androidconcepts.advancedConcurrency.producerConsumer.example4.Dish;
4 |
5 | import java.util.concurrent.ArrayBlockingQueue;
6 | import java.util.concurrent.BlockingQueue;
7 |
8 | public class BQDishRackExample5 {
9 |
10 | private final BlockingQueue washedDishesRack = new ArrayBlockingQueue(3);
11 | private final BlockingQueue cleanedDishesRack = new ArrayBlockingQueue(3);
12 |
13 | private final Runnable dishWasher = () -> {
14 | int id = 0;
15 |
16 | while (true) {
17 | Dish dishWashed = new Dish(++id);
18 |
19 | try {
20 | washedDishesRack.put(dishWashed);
21 | System.out.println("dish washed : " + dishWashed.getId());
22 | } catch (InterruptedException e) {
23 | e.printStackTrace();
24 | }
25 |
26 | if (id > 10000) {
27 | return;
28 | }
29 | }
30 | };
31 |
32 | private final Runnable dishCleaner = () -> {
33 | while (true) {
34 | try {
35 | Dish dishCleaned = washedDishesRack.take();
36 | System.out.println("dish cleaned : " + dishCleaned.getId());
37 | cleanedDishesRack.put(dishCleaned);
38 | } catch (InterruptedException e) {
39 | e.printStackTrace();
40 | }
41 | }
42 | };
43 |
44 | private final Runnable dishDryer = () -> {
45 | while (true) {
46 | try {
47 | Dish dishDried = cleanedDishesRack.take();
48 | System.out.println("dish dried : " + dishDried.getId());
49 | } catch (InterruptedException e) {
50 | e.printStackTrace();
51 | }
52 | }
53 | };
54 |
55 | public void startExecution() {
56 | new Thread(dishWasher).start();
57 | new Thread(dishCleaner).start();
58 | new Thread(dishDryer).start();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/cancellation/coroutines/learning1/CoroutineCancelActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.cancellation.coroutines.learning1
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import android.os.Bundle
5 | import android.util.Log
6 | import com.example.androidconcepts.R
7 | import kotlinx.coroutines.CoroutineScope
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.cancelChildren
10 | import kotlinx.coroutines.launch
11 |
12 | class CoroutineCancelActivity : AppCompatActivity() {
13 |
14 | private val useCase = PerformMoneyTransactionUseCase()
15 | private val scope = CoroutineScope(Dispatchers.Main.immediate)
16 |
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | setContentView(R.layout.activity_coroutine_cancel)
20 |
21 | scope.launch(Dispatchers.IO) {
22 | val result = useCase.performMoneyTransaction()
23 | Log.d("debug", "result from usecase : $result")
24 | }
25 | }
26 |
27 | override fun onPause() {
28 | super.onPause()
29 | scope.coroutineContext.cancelChildren()
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/cancellation/coroutines/learning1/PerformMoneyTransactionUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.cancellation.coroutines.learning1
2 |
3 | import android.util.Log
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.withContext
6 | import java.io.InputStream
7 | import java.net.URL
8 | import java.net.URLConnection
9 |
10 | class PerformMoneyTransactionUseCase {
11 |
12 | suspend fun performMoneyTransaction(): String {
13 | val result = performMoneyTransferNetworkCalls()
14 |
15 | Log.d("debug", "network call result received : $result")
16 |
17 | saveResultInDb(result)
18 |
19 | return "transaction done successfully!"
20 | }
21 |
22 | private suspend fun performMoneyTransferNetworkCalls(): String = withContext(Dispatchers.IO) {
23 | repeat(5) {
24 | Log.d("debug", "itr : $it")
25 | blockingDelay(1000)
26 | }
27 |
28 | Log.d("debug", "network calls done, returning result")
29 | return@withContext "done success"
30 | }
31 |
32 | private suspend fun saveResultInDb(result: String) = withContext(Dispatchers.IO) {
33 | Log.d("debug", " saving result started")
34 | blockingDelay(1000)
35 | Log.d("debug", " saved result started")
36 | }
37 | }
38 |
39 | fun blockingDelay(time: Long) {
40 | val finalTime = System.currentTimeMillis() + time
41 | var counter = 0;
42 | while (System.currentTimeMillis() < finalTime) {
43 | counter++;
44 | }
45 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/cancellation/coroutines/learning1/learning1.md:
--------------------------------------------------------------------------------
1 | This is the observed logs of Learning1 :
2 | 1. debug 0
3 | 2. debug 1
4 | 3. // press home, child coroutine gets cancelled.
5 | 4. debug 2
6 | 5. debug 3
7 | 6. debug 4
8 | 7. network calls done, returning result
9 |
10 | ->
11 | Given that `performMoneyTransferNetworkCalls` doesn't support cooperative cancellation i.e for eg: isActive check in repeat loop, it declares withContext explicitly.
12 | This withContext has a major role in our following observations.
13 |
14 | ->
15 | Even though both the suspend methods 'performMoneyTransferNetworkCalls' and 'saveResultInDb' doesn't support cooperative cancellations explicitly (use of ensureActive/isActive),
16 | When the parent scope is cancelled second method saveResultInDb doesn't executes. In fact even Log.d("debug", "network call result received : $result") doesn't executes.
17 |
18 | ->
19 | Give that `performMoneyTransaction` doesn't use a withContext but has suspend methods (which throws CancellationException) inside it, notice that in this block of code in activity :
20 | line 1 `val result = useCase.performMoneyTransfer()`
21 | line 2 `Log.d("debug", "result from usecase : $result")`
22 | After cancelling the scope, line 2 doesn't executes.
23 |
24 | ->
25 | 1. CancellationExceptions : These are the main reason for cancellations of the suspend functions execution and hence of coroutines.
26 | 2. The main reason in the code that even without using isActive when only withContext is present then coroutines still cancels after execution of blocking code is that withContext internally checks for isActive,
27 | But that only happens when while loop is finished.
28 |
29 | ->
30 | This means that for a coroutine of format :
31 | line 1 : suspend_fun1()
32 | line 2 : // the nature of code doesn't matter here...
33 | Even if suspend fun1 uses only withContext (or any inbuilt cancellation checking function) with blocking and non cooperative code inside it, if the parent scope is cancelled then the line just after fun1 will not execute.
34 | Same apply for the functions which checks for isActive() inside them.
35 |
36 | ->
37 | The fact that one cannot predict the nature of suspend methods (throws cancellation or not, if yes then immediately or after executing blocking code) by just seeing their name makes coroutines very complex.
38 |
39 | If one catches the cancellation exception of some suspend method then its not predictable that the
40 | exception will be thrown immediately after cancelling the coroutine or after 5 seconds (suppose you do view binding stuff inside the catch then you encounter typical binding is null exception).
41 |
42 | ->
43 | Few must to perform exercises and questions to think on :
44 | 1. What happens when we remove withContext from `performMoneyTransferNetworkCalls`.
45 | 2. What happens if we add a suspend method inside `performMoneyTransferNetworkCalls` inside the repeat loop.
46 | 3. What happens if we add a suspend method inside `performMoneyTransferNetworkCalls` after the repeat loop.
47 | 4. Why not use withContext in only `performMoneyTransaction` and not in the child suspend methods.
48 | 5. Remove all withContexts.
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/cancellation/rxjava/learning1/PerformMoneyTransactionRxUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.cancellation.rxjava.learning1
2 |
3 | import android.util.Log
4 | import com.example.androidconcepts.cancellation.coroutines.learning1.blockingDelay
5 | import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
6 | import io.reactivex.rxjava3.core.Observable
7 | import io.reactivex.rxjava3.core.Observer
8 | import io.reactivex.rxjava3.core.Scheduler
9 | import io.reactivex.rxjava3.disposables.Disposable
10 | import io.reactivex.rxjava3.schedulers.Schedulers
11 |
12 | class PerformMoneyTransactionRxUseCase {
13 |
14 | interface Listener {
15 | fun onSuccess(result: String)
16 | fun onError(reason: String)
17 | }
18 |
19 | private var disposable: Disposable? = null
20 |
21 | // Doubt : is the corresponding activity/fragment leaked? if yes till when? what about in case this is disposed?
22 |
23 | fun performMoneyTransactionAsync(callback: Listener) {
24 | disposable = Observable.just(1).map {
25 |
26 | val result = performMoneyTransferNetworkCalls()
27 |
28 | Log.d("debug", "network call result received : $result")
29 |
30 | saveResultInDb(result)
31 |
32 | return@map "transaction done successfully!"
33 | }.map {
34 |
35 | Log.d("debug", "second map result : $it")
36 | return@map it
37 |
38 | }.subscribeOn(Schedulers.io())
39 | .observeOn(AndroidSchedulers.mainThread())
40 | .subscribe(/* onNext */ { result ->
41 |
42 | Log.d("debug", "subscribe onNext : result $result")
43 |
44 | callback.onSuccess(result)
45 |
46 | }, /* onError */ { throwable ->
47 | Log.d("debug", "subscribe onError : reason ${throwable.message}")
48 | callback.onError(throwable.message ?: "some error occurred!")
49 | }, /* onComplete */ {
50 | Log.d("debug", "subscribe onComplete")
51 | })
52 | }
53 |
54 | private fun performMoneyTransferNetworkCalls(): String {
55 | repeat(5) {
56 | Log.d("debug", "repeat : $it isDisposed : ${disposable?.isDisposed}}")
57 | blockingDelay(1000)
58 | }
59 |
60 | return "Success"
61 | }
62 |
63 | private fun saveResultInDb(result: String) {
64 | Log.d("debug", " saving result started")
65 | blockingDelay(1000)
66 | Log.d("debug", " saved result")
67 | }
68 |
69 | fun cancelTransaction() {
70 | disposable?.dispose()
71 | }
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/cancellation/rxjava/learning1/RxCancelActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.cancellation.rxjava.learning1
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import android.os.Bundle
5 | import android.util.Log
6 | import com.example.androidconcepts.R
7 |
8 | class RxCancelActivity : AppCompatActivity() {
9 | private val usecase = PerformMoneyTransactionRxUseCase()
10 |
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | setContentView(R.layout.activity_rx_cancel)
14 |
15 | usecase.performMoneyTransactionAsync(object: PerformMoneyTransactionRxUseCase.Listener {
16 | override fun onSuccess(result: String) {
17 | Log.d("debug", "activity : onSuccess : $result")
18 | }
19 |
20 | override fun onError(reason: String) {
21 | Log.d("debug", "activity : onError : $reason")
22 | }
23 | })
24 | }
25 |
26 | override fun onPause() {
27 | super.onPause()
28 | usecase.cancelTransaction()
29 | }
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/cancellation/rxjava/learning1/learning1.md:
--------------------------------------------------------------------------------
1 | This is the observed logs of Learning1 :
2 |
3 | 1. debug repeat : 0 isDisposed : false
4 | 2. debug repeat : 1 isDisposed : false
5 | 3. // press home, disposable gets disposed.
6 | 4. debug repeat : 2 isDisposed : true
7 | 5. debug repeat : 3 isDisposed : true
8 | 6. debug repeat : 4 isDisposed : true
9 | 7. network call result received : Success
10 | 8. saving result started
11 | 9. saved result
12 | 10. second map result : transaction done successfully!
13 |
14 | From the above observation of logs, we can see that in rxJava execution gets cancelled only when it
15 | comes to onNext or onError (subscribe callbacks), It keeps on running even in the next map operator.
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/cancellation/threads/ThreadInterruption1UseCase.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.cancellation.threads
2 |
3 | import android.util.Log
4 | import com.example.androidconcepts.cancellation.coroutines.learning1.blockingDelay
5 |
6 | class ThreadInterruption1UseCase {
7 |
8 | private var thread: Thread? = null
9 |
10 | fun start() {
11 | thread = Thread {
12 |
13 | // even if thread is interrupted the runnable still completes unless
14 | // we use apis like wait() which throws InterruptedException explicitly.
15 | repeat(5) {
16 | Log.d("thread debug", "i = $it")
17 | blockingDelay(1000L)
18 | }
19 |
20 | // if the thread was interrupted earlier or while waiting, then an interrupted exception is thrown.
21 | synchronized(this) {
22 | (this as Object).wait()
23 | }
24 | }
25 |
26 | thread!!.start()
27 | }
28 |
29 | fun cancel() {
30 | thread?.interrupt()
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/common/BaseObservable.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.common
2 |
3 | import java.util.*
4 |
5 | interface Observable {
6 | fun registerListener(listener: ListenerClass)
7 | fun unregisterListener(listener: ListenerClass)
8 | }
9 |
10 | open class BaseObservable : Observable {
11 |
12 | private val listeners: MutableList by lazy {
13 | Collections.synchronizedList(mutableListOf())
14 | }
15 |
16 | override fun registerListener(listener: ListenerClass) {
17 | this.listeners.add(listener)
18 | }
19 |
20 | override fun unregisterListener(listener: ListenerClass) {
21 | this.listeners.remove(listener)
22 | }
23 |
24 | protected fun getAllListeners(): List {
25 | return listeners
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/common/BgThreadPoster.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.common
2 |
3 | import java.util.concurrent.ExecutorService
4 | import java.util.concurrent.Executors
5 | import java.util.concurrent.Future
6 | import java.util.concurrent.SynchronousQueue
7 | import java.util.concurrent.ThreadPoolExecutor
8 | import java.util.concurrent.TimeUnit
9 |
10 | open class BgThreadPoster {
11 | protected open val taskExecutor: ExecutorService = ThreadPoolExecutor(
12 | /* corePoolSize = */ 3,
13 | /* maximumPoolSize = */ Int.MAX_VALUE,
14 | /* keepAliveTime = */ 60,
15 | /* unit = */ TimeUnit.SECONDS,
16 | /* workQueue = */ SynchronousQueue()
17 | )
18 |
19 | fun post(runnable: Runnable): Future<*> {
20 | return taskExecutor.submit(runnable)
21 | }
22 | }
23 |
24 | class BgThreadPosterDualThreaded : BgThreadPoster() {
25 | // default override shows taskExecutor: ExecutorService get() = ...
26 | // it will return new executor each time. this mistake wasted 20mins :/
27 | override val taskExecutor: ExecutorService = Executors.newFixedThreadPool(2)
28 | }
29 |
30 | class BgThreadPosterSingleThreaded : BgThreadPoster() {
31 | // default override shows taskExecutor: ExecutorService get() = ...
32 | // it will return new executor each time. this mistake wasted 20mins :/
33 | override val taskExecutor: ExecutorService = Executors.newSingleThreadExecutor()
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/common/EmitOnRegisterObservable.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.common
2 |
3 | abstract class EmitOnRegisterObservable constructor(
4 | private val uiThreadPoster: UiThreadPoster
5 | ) : BaseObservable() {
6 |
7 | override fun registerListener(listener: ListenerClass) {
8 | super.registerListener(listener)
9 | notifyObserverOnRegistration(listener)
10 | }
11 |
12 | private fun isMainThread(): Boolean {
13 | return Thread.currentThread().id == 1L
14 | }
15 |
16 | abstract fun notifyObserverOnRegistration(listener: ListenerClass)
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/common/UiThreadPoster.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.common
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 | import java.util.concurrent.CountDownLatch
6 | import java.util.concurrent.TimeUnit
7 |
8 | open class UiThreadPoster {
9 | private val handler: Handler? = getUiHandler() // returns null only in case of test double
10 |
11 | protected open fun getUiHandler(): Handler? {
12 | return Handler(Looper.getMainLooper())
13 | }
14 |
15 | open fun post(runnable: Runnable) {
16 | handler!!.post(runnable)
17 | }
18 |
19 | open fun postDelayed(delayTime: Long, runnable: Runnable) {
20 | handler!!.postDelayed(runnable, delayTime)
21 | }
22 |
23 | open fun postDelayedAndWait(delayTime: Long, awaitTime: Long, runnable: Runnable) {
24 | val latch = CountDownLatch(1)
25 |
26 | handler!!.postDelayed({
27 | runnable.run()
28 | latch.countDown()
29 | }, delayTime)
30 |
31 | latch.await(awaitTime, TimeUnit.SECONDS)
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/coroutines/learning1/CoroutineLearning1Controller.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.coroutines.learning1
2 |
3 | import com.example.androidconcepts.coroutines.learning1.FibonacciUseCaseUsingCallback.*
4 | import kotlinx.coroutines.CoroutineScope
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.SupervisorJob
7 | import kotlinx.coroutines.launch
8 |
9 | private class CoroutineLearning1Controller constructor(
10 | private val fibonacciUseCaseUsingCoroutine: FibonacciUseCaseUsingCoroutine,
11 | private val fibonacciUseCaseUsingCallback: FibonacciUseCaseUsingCallback
12 | ) {
13 |
14 | private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
15 |
16 | fun fetchFibonacciNumber() {
17 | // usage of coroutines
18 | coroutineScope.launch {
19 | val result = fibonacciUseCaseUsingCoroutine.getFibonacciNumber(10)
20 | }
21 |
22 | // usage of callback
23 | fibonacciUseCaseUsingCallback.fetchFibonacciNumberAsync(10, object : Callback {
24 | override fun onResult(fibonacciNumber: Int) {
25 | val result = fibonacciNumber
26 | }
27 | })
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/coroutines/learning1/FibonacciUseCases.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.coroutines.learning1
2 |
3 | import com.example.androidconcepts.common.BgThreadPoster
4 | import com.example.androidconcepts.common.UiThreadPoster
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.withContext
7 | import java.util.concurrent.Executors
8 | import java.util.concurrent.Future
9 |
10 | class FibonacciUseCaseUsingCoroutine {
11 | suspend fun getFibonacciNumber(n: Int): Int = withContext(Dispatchers.Default) {
12 | return@withContext when (n) {
13 | 0 -> 0
14 | 1 -> 1
15 | else -> getFibonacciNumber(n - 1) + getFibonacciNumber(n - 2)
16 | }
17 | }
18 | }
19 |
20 | class FibonacciUseCaseUsingCallback constructor(
21 | private val bgThreadPoster: BgThreadPoster = BgThreadPoster(),
22 | private val uiThreadPoster: UiThreadPoster = UiThreadPoster()
23 | ) {
24 |
25 | interface Callback {
26 | fun onResult(fibonacciNumber: Int)
27 | }
28 |
29 | fun fetchFibonacciNumberAsync(n: Int, callback: Callback) = bgThreadPoster.post {
30 | val result = getFibonacciNumber(n)
31 | uiThreadPoster.post {
32 | callback.onResult(fibonacciNumber = result)
33 | }
34 | }
35 |
36 | private fun getFibonacciNumber(n: Int): Int {
37 | return when (n) {
38 | 0 -> 0
39 | 1 -> 1
40 | else -> getFibonacciNumber(n - 1) + getFibonacciNumber(n - 2)
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/coroutines/learning1/Learning1.md:
--------------------------------------------------------------------------------
1 | Learning1 demonstrates difference in usage between coroutines and bare callbacks.
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/coroutines/learning2/CoroutineLearning2Activity.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.coroutines.learning2
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import android.os.Bundle
5 | import com.example.androidconcepts.R
6 | import kotlinx.coroutines.*
7 |
8 | class CoroutineLearning2Activity : AppCompatActivity() {
9 |
10 | private val whileTrueLoopRunner: WhileTrueLoopRunner = WhileTrueLoopRunner()
11 |
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | setContentView(R.layout.activity_coroutine_learning2)
15 |
16 | CoroutineScope(SupervisorJob()).launch {
17 | withContext(Dispatchers.Main.immediate) {
18 | whileTrueLoopRunner.runWhileTrueLoop()
19 | }
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/coroutines/learning2/WhileTrueLoopRunner.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.coroutines.learning2
2 |
3 | class WhileTrueLoopRunner {
4 | suspend fun runWhileTrueLoop() {
5 | while (true) {
6 | /* no-op */
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter2_threadSafety/atomicSyncBug/CorrectAtomicServer.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter2_threadSafety.atomicSyncBug;
2 |
3 | import com.example.androidconcepts.jcip.common.ThreadContextSwitchTrigger;
4 |
5 | import java.util.concurrent.atomic.AtomicLong;
6 |
7 | /**
8 | * see AtomicServerTest.java
9 | */
10 | public class CorrectAtomicServer {
11 |
12 | private final AtomicLong hits = new AtomicLong(0);
13 |
14 | private final ThreadContextSwitchTrigger contextSwitchTrigger = new ThreadContextSwitchTrigger(100L);
15 |
16 | synchronized void performCalculation() {
17 | if (hits.get() < 10) { // hits count should not get greater than 10.
18 | contextSwitchTrigger.trigger();
19 | hits.incrementAndGet();
20 | }
21 | }
22 |
23 | public Long getHits() {
24 | return hits.get();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter2_threadSafety/atomicSyncBug/WrongAtomicServer.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter2_threadSafety.atomicSyncBug;
2 |
3 | import com.example.androidconcepts.jcip.common.ThreadContextSwitchTrigger;
4 |
5 | import java.util.concurrent.atomic.AtomicLong;
6 |
7 | /**
8 | * see AtomicServerTest.java
9 | */
10 | public class WrongAtomicServer {
11 |
12 | private final AtomicLong hits = new AtomicLong(0);
13 |
14 | private ThreadContextSwitchTrigger contextSwitchTrigger = new ThreadContextSwitchTrigger(100L);
15 |
16 | void performCalculation() {
17 | if (hits.get() < 10) { // hits count should not get greater than 10.
18 | contextSwitchTrigger.trigger();
19 | hits.incrementAndGet();
20 | }
21 | }
22 |
23 | public Long getHits() {
24 | return hits.get();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter2_threadSafety/intrinsicLocks/HomeWidget.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter2_threadSafety.intrinsicLocks;
2 |
3 | /**
4 | * see HomeWidgetTest.java
5 | */
6 | public class HomeWidget extends Widget {
7 |
8 | @Override
9 | synchronized void performTask() {
10 | System.out.println("HomeWidget performing task");
11 | super.performTask();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter2_threadSafety/intrinsicLocks/Widget.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter2_threadSafety.intrinsicLocks;
2 |
3 |
4 | public class Widget {
5 | synchronized void performTask() {
6 | System.out.println("Widget performing task");
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter2_threadSafety/intrinsicLocks/learning.md:
--------------------------------------------------------------------------------
1 | Objects in java work as intrinsic locks. Intrinsic Locks are reentrant in nature. If a thread is
2 | holding a lock A and it again request it to hold, then in reentrant nature they will get a hold of
3 | it. Reentrant locks are held pr thread.
4 |
5 | In the case of HomeWidget the lock is "this" which is reentrant in nature, hence the performTask
6 | method doesn't freeze. If it wasn't so then the thread would go in deadlock.
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter2_threadSafety/sync/SyncServer.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter2_threadSafety.sync;
2 |
3 | public class SyncServer {
4 | public synchronized void perform1() {
5 | while (true) {
6 | // no op
7 | }
8 | }
9 |
10 | public synchronized void perform2() {
11 | System.out.println("perform2");
12 | }
13 |
14 | public synchronized void perform3() {
15 | System.out.println("perform3");
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter2_threadSafety/syncPerfIssue/FastServer.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter2_threadSafety.syncPerfIssue;
2 |
3 | import com.example.androidconcepts.jcip.common.ThreadContextSwitchTrigger;
4 |
5 | public class FastServer {
6 | private int hits;
7 | private int tenthHits;
8 |
9 | private final ThreadContextSwitchTrigger contextSwitchTrigger = new ThreadContextSwitchTrigger(100L);
10 |
11 | public void performCalculations() {
12 | synchronized (this) {
13 | hits++;
14 | }
15 |
16 | int result = performFactorization();
17 |
18 | if (result % 10 == 0) {
19 | synchronized (this) {
20 | hits--;
21 | }
22 | }
23 | }
24 |
25 | private int performFactorization() {
26 | contextSwitchTrigger.trigger();
27 | return 10;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter2_threadSafety/syncPerfIssue/SlowServer.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter2_threadSafety.syncPerfIssue;
2 |
3 | import com.example.androidconcepts.jcip.common.ThreadContextSwitchTrigger;
4 |
5 | public class SlowServer {
6 |
7 | private int hits;
8 | private int tenthHits;
9 |
10 | private final ThreadContextSwitchTrigger contextSwitchTrigger = new ThreadContextSwitchTrigger(100L);
11 |
12 | public synchronized void performCalculations() {
13 | hits++;
14 | int result = performFactorization();
15 | if (result % 10 == 0) {
16 | hits--;
17 | }
18 | }
19 |
20 | private int performFactorization() {
21 | contextSwitchTrigger.trigger();
22 | return 10;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter3_sharingObjects/escape/WrongPublishAndEscapeExample1.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter3_sharingObjects.escape;
2 |
3 | import java.util.HashSet;
4 | import java.util.Set;
5 |
6 | public class WrongPublishAndEscapeExample1 {
7 | // thread-B might see this to be null even when initialize() is called on thread-A
8 | // due to visibility issues in discussed visibility package.
9 | public Set secrets;
10 |
11 | public void initialize() { // acts as setter method
12 | secrets = new HashSet<>();
13 | }
14 |
15 | private final String[] states = new String[]{"1", "2"};
16 |
17 | // states array escaped and can be mutated by the caller.
18 | public String[] getStates() {
19 | return states;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter3_sharingObjects/visibility/CorrectVisibilityExample3.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter3_sharingObjects.visibility;
2 |
3 | public class CorrectVisibilityExample3 {
4 | private boolean ready = false;
5 | private int value = 0;
6 |
7 | private void startLooping() {
8 | new Thread(() -> {
9 | while (true) {
10 | synchronized (this) {
11 | // if ready and value are written prior to acquiring this lock then everything visible to previous-lock-acquiring-thread (here its main thread) will become visible to this thread.
12 | // hence, here we will always see value = 40 with ready = true and not 0 and true.
13 | if (!ready)
14 | Thread.yield();
15 | else
16 | break;
17 | }
18 | }
19 |
20 | System.out.println("value " + value);
21 | }).start();
22 | }
23 |
24 | public void startExecution() {
25 | startLooping();
26 |
27 | System.out.println("changing data");
28 |
29 | synchronized (this) {
30 | // this can still be reorder but now it won't affect the correctness of our code.
31 | value = 40;
32 | ready = true;
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter3_sharingObjects/visibility/ForwardSubstitutionExample.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter3_sharingObjects.visibility;
2 |
3 | /**
4 | * Not given in jcip but in java docx https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html
5 | */
6 | public class ForwardSubstitutionExample {
7 | // see the fig 17.3 in the link given.
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter3_sharingObjects/visibility/Learning1.md:
--------------------------------------------------------------------------------
1 | 1. Operations done in a thread A might be rearranged and become visible to other threads in the
2 | different order or might not be visible at all. This can cause heavy complications in a program
3 | logic.
4 |
5 | 2. 64 bit DataTypes like double and long are treated as 2 32 bits. Hence other threads might read
6 | incorrect value (first 32 bit or second 32 bit).
7 |
8 | 3. To reason for correct visibility and ordering in concurrent programs we need Happens Before (hb)
9 | guarantees.
10 |
11 | 4. If one action happens-before another, then the first is visible to and ordered before the second.
12 |
13 | 5. It should be noted that the presence of a happens-before relationship between two actions does
14 | not necessarily imply that they have to take place in that order in an implementation. If the
15 | reordering produces results consistent with a legal execution, it is not illegal.
16 |
17 | 6. Everything Thread-A did in or prior to a synchronized block is visible to Thread-B when it
18 | executes a synchronized block guarded by the same lock. That's why we synchronize getters and
19 | setters (not to ensure atomicity but visibility).
20 |
21 | 7. Use volatile variables only when they simplify implementing and verifying your synchronization
22 | policy; avoid using volatile variables when verifying correctness would require subtle reasoning
23 | about visibility.
24 |
25 | 8. see https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter3_sharingObjects/visibility/NoVisibilityExample.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter3_sharingObjects.visibility;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Iterator;
5 | import java.util.List;
6 | import java.util.ListIterator;
7 | import java.util.concurrent.atomic.AtomicInteger;
8 |
9 | public class NoVisibilityExample {
10 | private boolean ready = false;
11 | private int value = 0;
12 |
13 | private void startLooping() {
14 | new Thread(() -> {
15 | while (!ready) { // loop may never break if updated value of ready is not visible.
16 | Thread.yield();
17 | }
18 |
19 | System.out.println("value " + value); // may print value : 0 here due to reordering.
20 | }).start();
21 | }
22 |
23 | public void startExecution() {
24 | startLooping();
25 |
26 | System.out.println("changing data");
27 |
28 | // Reordering can happen in this :
29 | // though we are making data change to first value and then ready, it might be the case
30 | // that ready becomes true prior to the value because writes to value and ready are independent to each other.
31 | value = 40;
32 | ready = true;
33 |
34 | // maybe rearranged to
35 | // ready = true;
36 | // value = 40;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/chapter3_sharingObjects/visibility/NoVisibilityExample2.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter3_sharingObjects.visibility;
2 |
3 | import java.util.concurrent.atomic.AtomicBoolean;
4 | import java.util.concurrent.atomic.AtomicInteger;
5 |
6 |
7 | public class NoVisibilityExample2 {
8 | // these are synchronized but on different locks which doesn't help in hb guarantee.
9 | private final AtomicBoolean ready = new AtomicBoolean(false);
10 | private final AtomicInteger value = new AtomicInteger(0);
11 |
12 | private void startLooping() {
13 | new Thread(() -> {
14 | while (!ready.get()) {
15 | Thread.yield();
16 | }
17 |
18 | System.out.println("value " + value.get()); // may print value : 0 here due to reordering.
19 | }).start();
20 | }
21 |
22 | public void startExecution() {
23 | startLooping();
24 |
25 | System.out.println("changing data");
26 |
27 | // these values can still be reordered as there is no happens before guarantee.
28 | value.set(40);
29 | ready.set(true);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/common/DelayedCallbackUseCase.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.common;
2 |
3 | import com.example.androidconcepts.common.BaseObservable;
4 | import com.example.androidconcepts.common.BgThreadPoster;
5 | import com.example.androidconcepts.common.UiThreadPoster;
6 |
7 | public class DelayedCallbackUseCase extends BaseObservable {
8 |
9 | public interface Listener {
10 | void onResult(int count);
11 | }
12 |
13 | private BgThreadPoster bgThreadPoster;
14 | private UiThreadPoster uiThreadPoster;
15 | private ThreadContextSwitchTrigger trigger = new ThreadContextSwitchTrigger(100L);
16 |
17 | private int counter = 0;
18 |
19 | public DelayedCallbackUseCase(BgThreadPoster bgThreadPoster, UiThreadPoster uiThreadPoster) {
20 | this.bgThreadPoster = bgThreadPoster;
21 | this.uiThreadPoster = uiThreadPoster;
22 | }
23 |
24 | public void fetchAsync(Long delayTime) {
25 | bgThreadPoster.post(() -> {
26 | trigger.trigger(delayTime);
27 |
28 | uiThreadPoster.post(() -> {
29 | for (Listener listener : getAllListeners()) {
30 | listener.onResult(++counter);
31 | }
32 | });
33 | });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/common/ThreadContextSwitchTrigger.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.common;
2 |
3 | import com.example.androidconcepts.common.BgThreadPoster;
4 |
5 | public class ThreadContextSwitchTrigger {
6 | private Long millis;
7 |
8 | public ThreadContextSwitchTrigger(Long millis) {
9 | this.millis = millis;
10 | }
11 |
12 | public void trigger() {
13 | long finalTime = System.currentTimeMillis() + millis;
14 | int count = 0;
15 | while (System.currentTimeMillis() < finalTime) {
16 | Thread.yield();
17 | count++;
18 | }
19 | }
20 |
21 | public void trigger(Long time) {
22 | long finalTime = System.currentTimeMillis() + time;
23 | int count = 0;
24 | while (System.currentTimeMillis() < finalTime) {
25 | Thread.yield();
26 | count++;
27 | }
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcip/extras/AndroidThreadSpammer.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.extras
2 |
3 | import android.util.Log
4 | import com.example.androidconcepts.jcip.common.ThreadContextSwitchTrigger
5 |
6 | /**
7 | * creates many threads to test for OOM errors
8 | */
9 | class AndroidThreadSpammer {
10 |
11 | fun execute() {
12 | val contextSwitchTrigger = ThreadContextSwitchTrigger(5000L)
13 |
14 | Thread {
15 | repeat(500) {
16 | Thread {
17 | Log.d("thread exp", "number : $it")
18 | contextSwitchTrigger.trigger()
19 | }.start()
20 | }
21 | }.start()
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcr/collections/Learning.md:
--------------------------------------------------------------------------------
1 | ArrayList and LinkedList :
2 |
3 | (Time of adding elements at an index i) is directly proportional to (the closeness of i from index
4 | 0). For eg : Adding at last index takes shortest time and adding at 0th index takes longest time.
5 | The same opposite in LinkedList i.e adding at the first index is shortest and at the last index is
6 | longest. Though around the middle index arrayList (80ms) takes much less time than linkedList (1sec)
7 |
8 | a. Adding 10^6 elements : ArrayList (20ms) LinkedList (120ms)
9 | b. After that adding 10^3 elements at middle index : ArrayList (80ms) LinkedList (1sec)
10 | c. After that adding 10^3 elements at 0 index : ArrayList (300ms) LinkedList (10ms)
11 |
12 | HashSet :
13 |
14 | Unsorted unique elements. Uses hashmap internally.
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcr/collections/collectionsInterface/CollectionInterfaceKt.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcr.collections.collectionsInterface
2 |
3 | class CollectionInterfaceKt {
4 | private val stringCollection: Collection = listOf("1", "2", "3")
5 | private val stringMutableCollection: MutableCollection = mutableListOf()
6 |
7 | fun execute() {
8 | stringCollection.forEach {
9 |
10 | }
11 |
12 | // no get() method is provided
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcr/collections/collectionsInterface/CollectionsInterfaceExample1.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcr.collections.collectionsInterface;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 |
6 | public class CollectionsInterfaceExample1 {
7 |
8 | private Collection stringCollection = new ArrayList<>();
9 |
10 | public Collection getStringCollection() {
11 | return stringCollection;
12 | }
13 |
14 | public void iterableExecute() {
15 | Iterable stringIterable = stringCollection;
16 |
17 | // can only be looped but cannot access index using get()
18 | int counter = 0;
19 | for (String s : stringIterable) {
20 | System.out.println("iteration " + counter + " : " + s);
21 | counter++;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcr/collections/listInterface/ListInterfaceExample1.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcr.collections.listInterface;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.HashSet;
6 | import java.util.List;
7 | import java.util.Set;
8 |
9 | public class ListInterfaceExample1 {
10 |
11 | public List getStringList_ArrayList() {
12 | List list = new ArrayList<>();
13 | list.add("1");
14 | list.add("2");
15 | return list;
16 | }
17 |
18 | public List getStringList_UnmodifiableList() {
19 | List list = new ArrayList<>();
20 | list.add("1");
21 | list.add("2");
22 | return Collections.unmodifiableList(list);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/jcr/collections/setInterface/SetInterfaceExample1.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcr.collections.setInterface;
2 |
3 | import java.util.HashSet;
4 | import java.util.Set;
5 |
6 | public class SetInterfaceExample1 {
7 |
8 | public Set getStringSetNumeric() {
9 | Set stringSet = new HashSet<>();
10 | stringSet.add("1");
11 | stringSet.add("3");
12 | stringSet.add("2");
13 | stringSet.add("5");
14 | stringSet.add("4");
15 | return stringSet;
16 | }
17 |
18 | public Set getStringSetAlphaNumeric() {
19 | Set stringSet = new HashSet<>();
20 | stringSet.add("1b");
21 | stringSet.add("3a");
22 | stringSet.add("2c");
23 | stringSet.add("5d");
24 | stringSet.add("4e");
25 | return stringSet;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/lifecycle/common/FragmentConfigChangeImpl.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.lifecycle.common
2 |
3 | import android.content.res.Configuration
4 | import androidx.fragment.app.Fragment
5 |
6 | fun Fragment.reattachFragForConfigChanges(newConfig: Configuration, superOnConfigChange: () -> Unit) {
7 | // means detach from ui (onDestroyView) not onDetach of lifecycle.
8 | requireActivity().supportFragmentManager.beginTransaction().detach(this)
9 | .commitAllowingStateLoss()
10 |
11 | // first onConfigChanged of fragment is detected then of activity.
12 | superOnConfigChange.invoke()
13 |
14 | // means attach to ui (onCreateView) not onAttach of lifecycle.
15 | // NOTE : fragment container with same id should be present in corresponding landscape ui.
16 | requireActivity().supportFragmentManager.beginTransaction().attach(this)
17 | .commitAllowingStateLoss()
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/lifecycle/common/LifecycleLoggerFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.lifecycle.common
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.util.Log
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.fragment.app.Fragment
10 |
11 | abstract class LifecycleLoggerFragment: Fragment() {
12 |
13 | abstract val TAG: String
14 |
15 | override fun onAttach(context: Context) {
16 | super.onAttach(context)
17 | Log.d(TAG, "onAttach")
18 | }
19 |
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 | super.onCreate(savedInstanceState)
22 | Log.d(TAG, "onCreate")
23 | }
24 |
25 | override fun onCreateView(
26 | inflater: LayoutInflater,
27 | container: ViewGroup?,
28 | savedInstanceState: Bundle?
29 | ): View? {
30 | Log.d(TAG, "onCreateView")
31 | return super.onCreateView(inflater, container, savedInstanceState)
32 | }
33 |
34 | override fun onStart() {
35 | super.onStart()
36 | Log.d(TAG, "onStart")
37 | }
38 |
39 | override fun onResume() {
40 | super.onResume()
41 | Log.d(TAG, "onResume")
42 | }
43 |
44 | override fun onPause() {
45 | super.onPause()
46 | Log.d(TAG, "onPause")
47 | }
48 |
49 | override fun onStop() {
50 | super.onStop()
51 | Log.d(TAG, "onStop")
52 | }
53 |
54 | override fun onDestroyView() {
55 | super.onDestroyView()
56 | Log.d(TAG, "onDestroyView")
57 | }
58 |
59 | override fun onDestroy() {
60 | super.onDestroy()
61 | Log.d(TAG, "onDestroy")
62 | }
63 |
64 | override fun onDetach() {
65 | super.onDetach()
66 | Log.d(TAG, "onDetach")
67 | }
68 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/lifecycle/manualRecreation/learning1/ManualConfig1Activity.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.lifecycle.manualRecreation.learning1
2 |
3 | import android.content.res.Configuration
4 | import androidx.appcompat.app.AppCompatActivity
5 | import android.os.Bundle
6 | import android.util.Log
7 | import android.widget.TextView
8 | import com.example.androidconcepts.R
9 |
10 | class ManualConfig1Activity : AppCompatActivity() {
11 |
12 | private val TAG = "config ManualConfig1Act"
13 | private var textView: TextView? = null
14 |
15 | private val randomNumber = (0..10).random() // TODO : test in process death.
16 |
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | Log.d(TAG, "onCreate")
20 | setContentViewAndViewReferences()
21 |
22 | supportFragmentManager.beginTransaction()
23 | .replace(R.id.fragment_container_view_tag, ManualConfig1Fragment())
24 | .commit()
25 | }
26 |
27 | override fun onConfigurationChanged(newConfig: Configuration) {
28 | super.onConfigurationChanged(newConfig)
29 |
30 | Log.d(TAG, "onConfigChanged")
31 | Log.d(TAG, "random after config change : $randomNumber") // remains same.
32 |
33 | setContentViewAndViewReferences()
34 | }
35 |
36 | private fun setContentViewAndViewReferences() {
37 | setContentView(R.layout.activity_manual_config1) // automatically sets layout for portrait/landscape.
38 | textView = findViewById(R.id.tv1)
39 | textView!!.text = "randomNo : $randomNumber"
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/lifecycle/manualRecreation/learning1/ManualConfig1Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.lifecycle.manualRecreation.learning1
2 |
3 | import android.content.res.Configuration
4 | import android.os.Bundle
5 | import android.util.Log
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.TextView
10 | import androidx.fragment.app.Fragment
11 | import androidx.lifecycle.Lifecycle
12 | import androidx.lifecycle.LifecycleEventObserver
13 | import androidx.lifecycle.LifecycleOwner
14 | import com.example.androidconcepts.R
15 | import com.example.androidconcepts.lifecycle.common.LifecycleLoggerFragment
16 |
17 | class ManualConfig1Fragment : LifecycleLoggerFragment() {
18 |
19 | override val TAG: String
20 | get() = "config fragment1"
21 |
22 | override fun onCreateView(
23 | inflater: LayoutInflater,
24 | container: ViewGroup?,
25 | savedInstanceState: Bundle?
26 | ): View {
27 | super.onCreateView(inflater, container, savedInstanceState)
28 | // automatically sets layout for portrait/landscape.
29 | return layoutInflater.inflate(
30 | /* resource = */ R.layout.fragment_manual_config_1,
31 | /* root = */ null
32 | )
33 | }
34 |
35 | override fun onConfigurationChanged(newConfig: Configuration) {
36 | Log.d(TAG, "onConfigChanged")
37 |
38 | // means detach from ui (onDestroyView) not onDetach of lifecycle.
39 | requireActivity().supportFragmentManager.beginTransaction().detach(this)
40 | .commitAllowingStateLoss()
41 |
42 | // first onConfigChanged of fragment is detected then of activity.
43 | super.onConfigurationChanged(newConfig)
44 |
45 | // means attach to ui (onCreateView) not onAttach of lifecycle.
46 | // NOTE : fragment container with same id should be present in corresponding landscape ui.
47 | requireActivity().supportFragmentManager.beginTransaction().attach(this)
48 | .commitAllowingStateLoss()
49 | }
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/lifecycle/manualRecreation/learning2/ManualConfig2Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.lifecycle.manualRecreation.learning2
2 |
3 | import android.content.res.Configuration
4 | import android.os.Bundle
5 | import android.util.Log
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.TextView
10 | import com.example.androidconcepts.R
11 | import com.example.androidconcepts.common.BgThreadPoster
12 | import com.example.androidconcepts.common.UiThreadPoster
13 | import com.example.androidconcepts.coroutines.learning1.FibonacciUseCaseUsingCallback
14 | import com.example.androidconcepts.jcip.common.DelayedCallbackUseCase
15 | import com.example.androidconcepts.lifecycle.common.LifecycleLoggerFragment
16 | import com.example.androidconcepts.lifecycle.common.reattachFragForConfigChanges
17 |
18 | class ManualConfig2Fragment : LifecycleLoggerFragment() {
19 |
20 | override val TAG: String
21 | get() = "config $counter"
22 |
23 | private var isInLandscape: Boolean = false
24 | private var viewMvc: ManualConfig2ViewMvc? = null
25 |
26 | private val delayedCallbackUseCase = DelayedCallbackUseCase(BgThreadPoster(), UiThreadPoster())
27 | private val delayedDataCallback = DelayedCallbackUseCase.Listener { counter ->
28 | viewMvc!!.bindData(counter)
29 | }
30 |
31 | private val randomNumber = (0..10).random()
32 | var counter = 1
33 |
34 | override fun onCreateView(
35 | inflater: LayoutInflater,
36 | container: ViewGroup?,
37 | savedInstanceState: Bundle?
38 | ): View {
39 | super.onCreateView(inflater, container, savedInstanceState)
40 |
41 | isInLandscape =
42 | requireContext().resources?.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE
43 |
44 | viewMvc = if (isInLandscape) {
45 | ManualConfig2ViewMvcLandscapeImpl(inflater, counter, randomNumber)
46 | } else {
47 | ManualConfig2ViewMvcPortraitImpl(inflater, counter, randomNumber)
48 | }
49 |
50 | viewMvc!!.setupUi()
51 |
52 | return viewMvc!!.getRootView()
53 | }
54 |
55 | override fun onStart() {
56 | super.onStart()
57 | delayedCallbackUseCase.registerListener(delayedDataCallback)
58 | delayedCallbackUseCase.fetchAsync(3000L)
59 | }
60 |
61 | override fun onStop() {
62 | super.onStop()
63 | delayedCallbackUseCase.unregisterListener(delayedDataCallback)
64 | }
65 |
66 | override fun onConfigurationChanged(newConfig: Configuration) {
67 | Log.d(TAG, "onConfigChanged $counter")
68 |
69 | reattachFragForConfigChanges(newConfig, superOnConfigChange = {
70 | super.onConfigurationChanged(newConfig)
71 | })
72 | }
73 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/lifecycle/manualRecreation/learning2/ManualConfig2ViewMvc.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.lifecycle.manualRecreation.learning2
2 |
3 | import android.view.View
4 | import com.example.androidconcepts.common.Observable
5 | import com.example.androidconcepts.lifecycle.manualRecreation.learning2.ManualConfig2ViewMvc.Listener
6 |
7 | interface ManualConfig2ViewMvc: Observable {
8 | interface Listener {
9 | // eg: on btn clicked
10 | }
11 |
12 | fun setupUi()
13 | fun bindData(count: Int)
14 | fun getRootView(): View
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/lifecycle/manualRecreation/learning2/ManualConfig2ViewMvcLandscapeImpl.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.lifecycle.manualRecreation.learning2
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.widget.TextView
6 | import com.example.androidconcepts.R
7 | import com.example.androidconcepts.common.BaseObservable
8 |
9 | class ManualConfig2ViewMvcLandscapeImpl constructor(
10 | layoutInflater: LayoutInflater,
11 | private val counter: Int,
12 | private val randomNo: Int
13 | ): ManualConfig2ViewMvc, BaseObservable() {
14 |
15 | private var rootView: View = layoutInflater.inflate(R.layout.fragment_manual_config_2, null)
16 | private var tvHeading: TextView = rootView.findViewById(R.id.tv3)
17 | private var tvBody: TextView = rootView.findViewById(R.id.tv4)
18 |
19 | override fun setupUi() {
20 | tvHeading.text = "headline counter : $counter no : $randomNo"
21 | tvBody.text = "body text"
22 | }
23 |
24 | override fun bindData(counter: Int) {
25 | tvHeading.text = "LANDSCAPE ViewMVC : heading data $counter"
26 | tvBody.text = "landscape body data"
27 | }
28 |
29 | override fun getRootView(): View {
30 | return rootView
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/lifecycle/manualRecreation/learning2/ManualConfig2ViewMvcPortraitImpl.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.lifecycle.manualRecreation.learning2
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.widget.TextView
6 | import com.example.androidconcepts.R
7 | import com.example.androidconcepts.common.BaseObservable
8 |
9 | class ManualConfig2ViewMvcPortraitImpl constructor(
10 | layoutInflater: LayoutInflater,
11 | private val counter: Int,
12 | private val randomNo: Int
13 | ): ManualConfig2ViewMvc, BaseObservable() {
14 |
15 | private var rootView: View = layoutInflater.inflate(R.layout.fragment_manual_config_2, null)
16 | private var tvHeading: TextView = rootView.findViewById(R.id.tv3)
17 |
18 | override fun setupUi() {
19 | tvHeading.text = "headline counter : $counter no : $randomNo"
20 | }
21 |
22 | override fun bindData(counter: Int) {
23 | tvHeading.text = "PORTRAIT ViewMVC : data fetched $counter"
24 | }
25 |
26 | override fun getRootView(): View {
27 | return rootView
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/lifecycle/manualRecreation/learning2/ManualConfigActivity2.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.lifecycle.manualRecreation.learning2
2 |
3 | import android.content.res.Configuration
4 | import androidx.appcompat.app.AppCompatActivity
5 | import android.os.Bundle
6 | import com.example.androidconcepts.R
7 |
8 | class ManualConfigActivity2 : AppCompatActivity() {
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 | setContentViewAndReferences()
12 |
13 | repeat(4) {
14 | val frag = ManualConfig2Fragment()
15 | frag.counter = it
16 | supportFragmentManager.beginTransaction()
17 | .replace(R.id.fragment_container_view_tag, frag, null)
18 | .addToBackStack(null)
19 | .commit()
20 | }
21 | }
22 |
23 | override fun onConfigurationChanged(newConfig: Configuration) {
24 | super.onConfigurationChanged(newConfig)
25 | setContentViewAndReferences()
26 | }
27 |
28 | private fun setContentViewAndReferences() {
29 | setContentView(R.layout.activity_manual_config2)
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/livedata/learning1/Learning1.md:
--------------------------------------------------------------------------------
1 | Learning1 demonstrates how livedata replaces callbacks in situations where we also store the data and notify listeners after that.
2 | [mValue = value]
3 | [notifyListeners()]
4 |
5 | it also internally handles all the required thread synchronisation.
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/livedata/learning1/LiveDataApiEndpointSync.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.livedata.learning1
2 |
3 | class LiveDataApiEndpointSync {
4 | fun fetchDataFromServer(): Int {
5 | return 100 // Let's say
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/livedata/learning1/after/LiveDataLearning1AfterController.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.livedata.learning1.after
2 |
3 | import androidx.lifecycle.LifecycleOwner
4 |
5 | class LiveDataLearning1AfterController constructor(
6 | private val useCase: LiveDataLearning1AfterUseCase,
7 | private val lifecycleOwner: LifecycleOwner
8 | ) {
9 |
10 | fun onStart() {
11 | subscribeToObservers()
12 | useCase.fetchDataFromServerAsync()
13 | useCase.fetchDataFromLocalDbAsync()
14 | }
15 |
16 | private fun subscribeToObservers() {
17 | useCase.cachedNumber.observe(lifecycleOwner) {
18 | /* no-op */
19 | }
20 | }
21 |
22 | fun onClick() {
23 | val cachedNumberValue = useCase.cachedNumber.value
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/livedata/learning1/after/LiveDataLearning1AfterUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.livedata.learning1.after
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import com.example.androidconcepts.common.BgThreadPoster
6 | import com.example.androidconcepts.common.UiThreadPoster
7 | import com.example.androidconcepts.livedata.learning1.LiveDataApiEndpointSync
8 |
9 | class LiveDataLearning1AfterUseCase constructor(
10 | private val apiEndPointSync: LiveDataApiEndpointSync,
11 | private val bgThreadPoster: BgThreadPoster = BgThreadPoster()
12 | ) {
13 | private val _cachedNumber = MutableLiveData()
14 | val cachedNumber: LiveData = _cachedNumber
15 |
16 | fun fetchDataFromServerAsync() = bgThreadPoster.post {
17 | val result = apiEndPointSync.fetchDataFromServer()
18 | _cachedNumber.postValue(result)
19 | }
20 |
21 | fun fetchDataFromLocalDbAsync() = bgThreadPoster.post {
22 | val result = apiEndPointSync.fetchDataFromServer()
23 | _cachedNumber.postValue(result)
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/livedata/learning1/before/LiveDataLearning1BeforeController.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.livedata.learning1.before
2 |
3 | class LiveDataLearning1BeforeController constructor(
4 | private val useCase: LiveDataLearning1BeforeUseCase
5 | ) {
6 | private lateinit var useCaseListener: LiveDataLearning1BeforeUseCase.Listener
7 |
8 | fun onStart() {
9 | subscribeToObservers()
10 | useCase.fetchDataFromServerAsync()
11 | useCase.fetchDataFromLocalDbAsync()
12 | }
13 |
14 | private fun subscribeToObservers() {
15 | if (::useCaseListener.isInitialized.not()) {
16 | initUseCaseListener()
17 | }
18 |
19 | useCase.registerListener(useCaseListener)
20 | }
21 |
22 | private fun initUseCaseListener() {
23 | useCaseListener = object : LiveDataLearning1BeforeUseCase.Listener {
24 | override fun onNumberCached(number: Int) {
25 | /* no-op */
26 | }
27 | }
28 | }
29 |
30 | fun onClick() {
31 | val cachedNumberValue = useCase.getCachedNumberValue()
32 | }
33 |
34 | fun onPause() {
35 | useCase.unregisterListener(useCaseListener)
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/livedata/learning1/before/LiveDataLearning1BeforeUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.livedata.learning1.before
2 |
3 | import com.example.androidconcepts.common.BaseObservable
4 | import com.example.androidconcepts.common.BgThreadPoster
5 | import com.example.androidconcepts.common.UiThreadPoster
6 | import com.example.androidconcepts.livedata.learning1.LiveDataApiEndpointSync
7 |
8 | class LiveDataLearning1BeforeUseCase constructor(
9 | private val apiEndPointSync: LiveDataApiEndpointSync,
10 | private val uiThreadPoster: UiThreadPoster = UiThreadPoster(),
11 | private val bgThreadPoster: BgThreadPoster = BgThreadPoster()
12 | ) : BaseObservable() {
13 |
14 | interface Listener {
15 | fun onNumberCached(number: Int)
16 | }
17 |
18 | private var cachedNumber: Int? = null // apart from notifying the cached value to the listeners, we are also storing it.
19 | private val lock: Any = Any()
20 |
21 | fun fetchDataFromServerAsync() = bgThreadPoster.post {
22 | val result = apiEndPointSync.fetchDataFromServer()
23 | setValueAndNotifyListeners(result)
24 | }
25 |
26 | fun fetchDataFromLocalDbAsync() = bgThreadPoster.post {
27 | val result = apiEndPointSync.fetchDataFromServer()
28 | setValueAndNotifyListeners(result)
29 | }
30 |
31 | private fun setValueAndNotifyListeners(value: Int) {
32 | synchronized(lock) {
33 | cachedNumber = value
34 | notifyListeners()
35 | }
36 | }
37 |
38 | private fun notifyListeners() {
39 | uiThreadPoster.post {
40 | for (listener in getAllListeners()) {
41 | listener.onNumberCached(cachedNumber!!)
42 | }
43 | }
44 | }
45 |
46 | fun getCachedNumberValue(): Int? {
47 | synchronized(lock) {
48 | return cachedNumber
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/livedata/learning2/ODHController.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.livedata.learning2
2 |
3 | import android.widget.Toast
4 |
5 | class ODHController constructor(
6 | private val odhUsecase: ODHUsecase
7 | ) {
8 |
9 | private val statusObserver = object : ObservableDataHolder.Observer {
10 | override fun onValueChanged(data: Int) {
11 | /* no-op */
12 | }
13 | }
14 |
15 | fun onClick() {
16 | odhUsecase.fetchData()
17 | }
18 |
19 | fun onStart() {
20 | odhUsecase.status.registerListener(statusObserver)
21 | }
22 |
23 | fun onStop() {
24 | odhUsecase.status.unregisterListener(statusObserver)
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/livedata/learning2/ODHUsecase.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.livedata.learning2
2 |
3 | import com.example.androidconcepts.common.BgThreadPoster
4 |
5 | class ODHUsecase constructor(
6 | private val bgThreadPoster: BgThreadPoster = BgThreadPoster()
7 | ) {
8 | val status = ObservableDataHolder()
9 |
10 | fun fetchData() {
11 | bgThreadPoster.post {
12 | Thread.sleep(3000L)
13 | status.setData(10) // no need to think about postValue/setValue
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/livedata/learning2/ObservableDataHolder.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.livedata.learning2
2 |
3 | import androidx.annotation.GuardedBy
4 | import com.example.androidconcepts.common.EmitOnRegisterObservable
5 | import com.example.androidconcepts.common.UiThreadPoster
6 | import com.example.androidconcepts.livedata.learning2.ObservableDataHolder.*
7 |
8 |
9 | /**
10 | * An observable data holder used in cases where we want to get a notification of the data when a observer is registered.
11 | * Typically used in cases where important notifications can miss due to unregistration of observers in onStop().
12 | *
13 | * Simple to use as registration doesn't require any viewLifecycleOwner hence it can be used for all purposes and not only as a UI-DataHolder.
14 | * Also we don't need to think about which one to use i.e setValue or postValue.
15 | *
16 | * @author Siddharth Sharma
17 | */
18 | @Suppress("UNCHECKED_CAST")
19 | class ObservableDataHolder constructor(
20 | initialValue: T? = null,
21 | private val notifyOnRegistration: Boolean = true,
22 | private val uiThreadPoster: UiThreadPoster = UiThreadPoster()
23 | ) : EmitOnRegisterObservable>(uiThreadPoster) {
24 |
25 | interface Observer {
26 | fun onValueChanged(data: T)
27 | }
28 |
29 | @GuardedBy("LOCK")
30 | private var data: Object
31 |
32 | private val NOT_SET = Object()
33 |
34 | init {
35 | if (initialValue == null)
36 | data = NOT_SET
37 | else
38 | data = initialValue as Object
39 | }
40 |
41 | private val LOCK = Object()
42 |
43 | /**
44 | * NOTE : On main thread, observers are notified via mainLooper.post api rather than notifying immediately.
45 | * TODO : what are the repercussions for this?
46 | */
47 | fun setData(newData: T) {
48 | synchronized(LOCK) {
49 | this.data = newData as Object
50 |
51 | uiThreadPoster.post {
52 | notifyAllListeners(newData)
53 | }
54 | }
55 | }
56 |
57 | fun getValue(): T? {
58 | synchronized(LOCK) {
59 | if (isNotSet())
60 | return null
61 | else
62 | return data as T
63 | }
64 | }
65 |
66 | fun isNotSet(): Boolean {
67 | synchronized(LOCK) {
68 | return data == NOT_SET
69 | }
70 | }
71 |
72 | private fun notifyAllListeners(newData: T) {
73 | for (listener in getAllListeners())
74 | listener.onValueChanged(newData)
75 | }
76 |
77 | override fun notifyObserverOnRegistration(listener: Observer) {
78 |
79 | if (isNotSet() || !notifyOnRegistration) return
80 |
81 | uiThreadPoster.post {
82 | listener.onValueChanged(data as T)
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/ood/fsm/login/LoginScreenController.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.ood.fsm.login
2 |
3 | class LoginScreenController constructor(
4 | private val userManager: UserManager
5 | ) {
6 |
7 | private lateinit var viewMvc: LoginViewMvc
8 |
9 | fun onStart() {
10 | viewMvc.registerListener(viewListener)
11 | userManager.registerListener(userManagerListener)
12 | }
13 |
14 | sealed class LoginState {
15 | sealed class WhatsAppLogin : LoginState() {
16 | object PROGRESS : WhatsAppLogin()
17 | object SUCCESS : WhatsAppLogin()
18 | object FAILURE : WhatsAppLogin()
19 | }
20 |
21 | sealed class OtpLogin : LoginState() {
22 | object PROGRESS : OtpLogin()
23 | object SUCCESS : OtpLogin()
24 | object FAILURE : OtpLogin()
25 | }
26 |
27 | sealed class GoogleLogin : LoginState() {
28 | object PROGRESS : GoogleLogin()
29 | object SUCCESS : GoogleLogin()
30 | object FAILURE : GoogleLogin()
31 | }
32 | }
33 |
34 | fun setStateAndHandle(newState: LoginState) {
35 | when (newState) {
36 | LoginState.WhatsAppLogin.SUCCESS -> {
37 | viewMvc.hideProgress()
38 | }
39 |
40 | LoginState.WhatsAppLogin.FAILURE -> {
41 | viewMvc.hideProgress()
42 | }
43 |
44 | LoginState.WhatsAppLogin.PROGRESS -> {
45 | viewMvc.showProgress()
46 | }
47 |
48 | LoginState.GoogleLogin.FAILURE -> {
49 |
50 | }
51 |
52 | LoginState.GoogleLogin.PROGRESS -> {
53 |
54 | }
55 |
56 | LoginState.GoogleLogin.SUCCESS -> {
57 |
58 | }
59 |
60 | LoginState.OtpLogin.FAILURE -> {
61 |
62 | }
63 |
64 | LoginState.OtpLogin.PROGRESS -> {
65 |
66 | }
67 |
68 | LoginState.OtpLogin.SUCCESS -> {
69 |
70 | }
71 | }
72 | }
73 |
74 | private val viewListener = object : LoginViewMvc.Listener {
75 | override fun onWhatsAppLoginBtnClicked() {
76 |
77 | }
78 |
79 | override fun onGoogleLoginBtnClicked() {
80 |
81 | }
82 |
83 | override fun onPhoneNoInputClicked() {
84 |
85 | }
86 |
87 | override fun onPhoneNoInputEntered() {
88 |
89 | }
90 |
91 | override fun onPhoneNoLoginBtnClicked() {
92 |
93 | }
94 | }
95 |
96 | private val userManagerListener = object : UserManager.Listener {
97 | override fun onLoginSuccess() {
98 | setStateAndHandle(LoginState.WhatsAppLogin.SUCCESS)
99 | }
100 |
101 | override fun onLoginFailure() {
102 | setStateAndHandle(LoginState.WhatsAppLogin.FAILURE)
103 | }
104 | }
105 |
106 | fun onStop() {
107 | viewMvc.unregisterListener(viewListener)
108 | userManager.unregisterListener(userManagerListener)
109 | }
110 |
111 | fun bindViewMvc(viewMvc: LoginViewMvc) {
112 | this.viewMvc = viewMvc
113 | }
114 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/ood/fsm/login/LoginViewMvc.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.ood.fsm.login
2 |
3 | import com.example.androidconcepts.common.BaseObservable
4 | import com.example.androidconcepts.common.Observable
5 |
6 | interface LoginViewMvc: Observable {
7 | interface Listener {
8 | fun onWhatsAppLoginBtnClicked()
9 |
10 | fun onGoogleLoginBtnClicked()
11 |
12 | fun onPhoneNoInputClicked()
13 | fun onPhoneNoInputEntered()
14 | fun onPhoneNoLoginBtnClicked()
15 | }
16 |
17 | fun showProgress()
18 | fun hideProgress()
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/ood/fsm/login/UserManager.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.ood.fsm.login
2 |
3 | import com.example.androidconcepts.common.BaseObservable
4 |
5 | class UserManager: BaseObservable() {
6 |
7 | interface Listener {
8 | fun onLoginSuccess()
9 | fun onLoginFailure()
10 | }
11 |
12 | fun login() {
13 | /* no-op */
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/ood/solid/o/learning1/Employee.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.ood.solid.o.learning1
2 |
3 | data class Employee(
4 | val name: String,
5 | val type: Int
6 | ) {
7 | companion object {
8 | const val FREELANCE = 1
9 | const val FULLTIME = 2
10 | const val INTERN = 3
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/ood/solid/o/learning1/Learning1.md:
--------------------------------------------------------------------------------
1 | Learning 1 demonstrates the usage of open close principle (ocp).
2 | Note :do not apply ocp in cases where we don't know what future changes can be.
3 | For eg: TextFormat can change (txt, docx etc) but in case of TextExtractor (extracts text from image) we don't what alternates of it can come in future
4 | (here we are not talking about internal apis of TextExtractor but the TextExtractor as a whole entity).
5 |
6 | ### cost of abstraction
7 | 1) Abstraction make your code more complex. eg : if you see BMW: Car() then immediately thoughts would come in mind that are there any other realizations of Car too?
8 | 2) Wrong abstractions can be extremely hard to fix later.
9 |
10 | Having said that, if you've got a new change from business and there's an opportunity to make the system resilient to such changes, then go ahead and refactor abstractions out and use ocp.
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/ood/solid/o/learning1/after/SalaryCalculatorAfter.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.ood.solid.o.learning1.after
2 |
3 | import com.example.androidconcepts.ood.solid.o.learning1.Employee
4 |
5 | class SalaryCalculatorAfter {
6 | private val taxCalculatorFactory = TaxCalculatorFactory()
7 |
8 | fun calculateSalary(employee: Employee) {
9 | val taxCalculator = taxCalculatorFactory.getTaxCalculator(employee)
10 | val tax = taxCalculator.getTax(employee)
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/ood/solid/o/learning1/after/TaxCalculator.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.ood.solid.o.learning1.after
2 |
3 | import com.example.androidconcepts.ood.solid.o.learning1.Employee
4 |
5 | interface TaxCalculator {
6 | fun getTax(employee: Employee): Long
7 | }
8 |
9 | class FullTimeEmployeeTaxCalculator : TaxCalculator {
10 | override fun getTax(employee: Employee): Long {
11 | return 100L
12 | }
13 | }
14 |
15 | class FreelanceEmployeeTaxCalculator : TaxCalculator {
16 | override fun getTax(employee: Employee): Long {
17 | return 200L
18 | }
19 | }
20 |
21 | class InternEmployeeTaxCalculator : TaxCalculator {
22 | override fun getTax(employee: Employee): Long {
23 | return 0L
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/ood/solid/o/learning1/after/TaxCalculatorFactory.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.ood.solid.o.learning1.after
2 |
3 | import com.example.androidconcepts.ood.solid.o.learning1.Employee
4 |
5 | class TaxCalculatorFactory {
6 | fun getTaxCalculator(employee: Employee): TaxCalculator {
7 | return when (employee.type) {
8 | Employee.FREELANCE -> FreelanceEmployeeTaxCalculator()
9 | Employee.FULLTIME -> FullTimeEmployeeTaxCalculator()
10 | Employee.INTERN -> InternEmployeeTaxCalculator()
11 | else -> { FullTimeEmployeeTaxCalculator() }
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/ood/solid/o/learning1/before/SalaryCalculatorBefore.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.ood.solid.o.learning1.before
2 |
3 | import com.example.androidconcepts.ood.solid.o.learning1.Employee
4 |
5 | class SalaryCalculatorBefore {
6 | fun calculateSalary(employee: Employee) {
7 | val tax = getTaxForEmployee(employee.type)
8 | }
9 |
10 | private fun getTaxForEmployee(employeeType: Int): Long {
11 | return when (employeeType) {
12 | Employee.FULLTIME -> {
13 | 100L
14 | }
15 | Employee.FREELANCE -> {
16 | 200L
17 | }
18 | Employee.INTERN -> {
19 | 0L
20 | }
21 | else -> {
22 | 200L
23 | }
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/rxJava/ownImpl/ReactObservable.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.rxJava.ownImpl
2 |
3 | import com.example.androidconcepts.common.UiThreadPoster
4 | import java.util.concurrent.Callable
5 | import java.util.concurrent.CountDownLatch
6 |
7 | class ReactObservable constructor(val data: ReactObservableData) {
8 |
9 | interface Observer {
10 | fun onNext(data: T)
11 | fun onError(throwable: Throwable)
12 | }
13 |
14 | private val observers = mutableListOf>()
15 | private lateinit var uiThreadPoster: UiThreadPoster
16 |
17 | fun setUiThreadPoster(uiThreadPoster: UiThreadPoster): ReactObservable {
18 | this.uiThreadPoster = uiThreadPoster
19 | return this
20 | }
21 |
22 | fun subscribe(observer: Observer) {
23 |
24 | // if UiThreadPoster is not set using setter method then this is always not initialized,
25 | // use TestDouble for unit tests.
26 | if (!::uiThreadPoster.isInitialized)
27 | uiThreadPoster = data.uiThreadPoster ?: UiThreadPoster()
28 |
29 | observers.add(observer)
30 |
31 | Thread {
32 | try {
33 | val result: T = data.callable.call()
34 | notifyObserversOfSuccess(result)
35 | } catch (e: Exception) {
36 | notifyObserversOfException(e)
37 | }
38 | }.start() // start emitting only when subscribed i.e cold stream.
39 | }
40 |
41 | private fun notifyObserversOfSuccess(result: T) {
42 | uiThreadPoster.post {
43 | observers.forEach {
44 | it.onNext(result)
45 | }
46 | }
47 | }
48 |
49 | private fun notifyObserversOfException(e: Exception) {
50 | uiThreadPoster.post {
51 | observers.forEach {
52 | it.onError(Throwable(e.message))
53 | }
54 | }
55 | }
56 |
57 |
58 | // ---------- operators -----------
59 |
60 | fun flatMap(mapper: (T) -> ReactObservable): ReactObservable {
61 | val callable = Callable {
62 |
63 | // bg thread
64 |
65 | val parentObs = this
66 | val latch = CountDownLatch(1)
67 |
68 | var result: ReactObservable? = null
69 |
70 | // get the data from observable, map to new observable and notify the latch.
71 | parentObs.subscribe(object : Observer {
72 | override fun onNext(data: T) {
73 | result = mapper.invoke(data)
74 | latch.countDown()
75 | }
76 |
77 | override fun onError(throwable: Throwable) {
78 | latch.countDown()
79 | }
80 | })
81 |
82 | latch.await()
83 |
84 | return@Callable result!!.data.callable.call()
85 | }
86 |
87 | // when subscribe is called on this returned ReactObservable then this callable is called
88 | // which internally subscribes to the parent observable.
89 | return fromCallable(
90 | callable = callable,
91 | uiThreadPoster = uiThreadPoster // pass current threadPoster to new observables
92 | )
93 | }
94 |
95 | companion object {
96 | fun fromCallable(
97 | callable: Callable,
98 | uiThreadPoster: UiThreadPoster? = null
99 | ): ReactObservable {
100 | return ReactObservable(
101 | ReactObservableData(callable, uiThreadPoster)
102 | )
103 | }
104 | }
105 |
106 | data class ReactObservableData(
107 | val callable: Callable,
108 | val uiThreadPoster: UiThreadPoster? = null
109 | )
110 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/rxJava/ownImpl/ReactObservableUsage1.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.rxJava.ownImpl
2 |
3 | import com.example.androidconcepts.cancellation.coroutines.learning1.blockingDelay
4 | import java.util.concurrent.Callable
5 |
6 | class ReactObservableUsage1 {
7 |
8 | fun fetchOBS(): ReactObservable {
9 | return ReactObservable.fromCallable(Callable {
10 | blockingDelay(1000L)
11 | return@Callable "1"
12 | })
13 | }
14 |
15 | fun mapToIntOBS(data: String): ReactObservable {
16 | return ReactObservable.fromCallable(Callable {
17 | blockingDelay(1000L)
18 | return@Callable data.toInt()
19 | })
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/ui/retroDesign/RetroLayout.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.ui.retroDesign
2 |
3 | import android.content.Context
4 | import android.content.res.Resources
5 | import android.graphics.Canvas
6 | import android.graphics.Color
7 | import android.graphics.Paint
8 | import android.util.AttributeSet
9 | import android.util.Log
10 | import android.util.TypedValue
11 | import android.view.LayoutInflater
12 | import android.view.MotionEvent
13 | import android.view.View
14 | import android.view.ViewPropertyAnimator
15 | import android.widget.TextView
16 | import android.widget.Toast
17 | import androidx.cardview.widget.CardView
18 | import androidx.constraintlayout.widget.ConstraintLayout
19 | import androidx.core.view.isVisible
20 | import com.example.androidconcepts.R
21 |
22 |
23 | class RetroLayout constructor(
24 | context: Context,
25 | attributeSet: AttributeSet?
26 | ) : CardView(context, attributeSet) {
27 |
28 | private val root: View
29 | private val contentContainer: CardView
30 | private val sideShadow: View
31 | private val bottomShadow: View
32 |
33 | init {
34 | root = LayoutInflater.from(context).inflate(R.layout.layout_retro_default, this, true)
35 | contentContainer = root.findViewById(R.id.cv_content)
36 | sideShadow = root.findViewById(R.id.view_rightShadow)
37 | bottomShadow = root.findViewById(R.id.view_bottomShadow)
38 | setCardBackgroundColor(Color.TRANSPARENT)
39 |
40 | cardElevation = 0f
41 | isClickable = true
42 | }
43 |
44 | private var lastClicked = System.currentTimeMillis()
45 | private val duration = 90L
46 |
47 | private var shimmer: RetroShimmerView? = null
48 |
49 | override fun performClick(): Boolean {
50 |
51 | val w = contentContainer.width
52 | val h = contentContainer.height
53 |
54 | if (shimmer == null) {
55 | shimmer = RetroShimmerView(context)
56 |
57 | shimmer!!.layoutParams = LayoutParams(w, h)
58 | shimmer!!.elevation = 12f
59 |
60 | contentContainer.addView(shimmer)
61 | }
62 |
63 | shimmer?.startAnim(w.toFloat())
64 |
65 | return super.performClick()
66 | }
67 |
68 | override fun onAttachedToWindow() {
69 | super.onAttachedToWindow()
70 |
71 | if (contentContainer.childCount > 0) return // assuming the target view is already added.
72 |
73 | // detach user's target view from current pos in hierarchy and attach to content container.
74 | // child at 0th pos is "root".
75 | val view = getChildAt(1)
76 | removeView(view)
77 |
78 | (contentContainer).addView(view)
79 |
80 | val isWidthMatchParent =
81 | layoutParams.width == android.view.ViewGroup.LayoutParams.MATCH_PARENT
82 |
83 | if (isWidthMatchParent) {
84 | // if match parent then we need to modify some attributes.
85 | getChildAt(0).layoutParams.width = android.view.ViewGroup.LayoutParams.MATCH_PARENT
86 |
87 | (contentContainer.layoutParams as ConstraintLayout.LayoutParams).setMargins(
88 | /* left = */ 0,
89 | /* top = */ 0,
90 | /* right = */ resources.getDimension(R.dimen.neopop_def_space).toInt() - 5,
91 | /* bottom = */ 0
92 | )
93 |
94 | contentContainer.layoutParams.width = android.view.ViewGroup.LayoutParams.MATCH_PARENT
95 | invalidate()
96 | }
97 | }
98 |
99 | private var animUpUnconsumed = false
100 |
101 | enum class STATE {
102 | PRESSING, PRESSED, RELEASING, RELEASED
103 | }
104 |
105 | private var currState: STATE = STATE.RELEASED
106 |
107 | override fun onTouchEvent(event: MotionEvent?): Boolean {
108 |
109 | when (event?.action) {
110 |
111 | MotionEvent.ACTION_DOWN -> {
112 |
113 | if (currState != STATE.RELEASED) return false
114 |
115 | if (System.currentTimeMillis() - lastClicked < (duration * 2) + /* offset */ 100L) return false
116 | lastClicked = System.currentTimeMillis()
117 |
118 | animateDown()
119 | return true
120 | }
121 |
122 | MotionEvent.ACTION_UP -> {
123 |
124 | if (currState == STATE.PRESSING) {
125 | animUpUnconsumed = true
126 | performClick()
127 | } else if (currState == STATE.PRESSED) {
128 | animateUp()
129 | performClick()
130 | }
131 |
132 | return true
133 | }
134 |
135 | MotionEvent.ACTION_MOVE -> {
136 | if (event.x !in 0f..width.toFloat() || event.y !in 0f..height.toFloat()) {
137 |
138 | if (currState == STATE.PRESSING) {
139 | animUpUnconsumed = true
140 | } else if (currState == STATE.PRESSED) {
141 | animateUp()
142 | }
143 |
144 | return true
145 | }
146 | }
147 | }
148 |
149 | return false
150 | }
151 |
152 | private val space = context.resources.getDimension(R.dimen.neopop_def_space)
153 | private val contentDisplacement = 2 * space / 3
154 | private val shadowDisplacement = space / 3
155 |
156 | private fun animateDown() {
157 | contentContainer.animate().translationXBy(contentDisplacement)
158 | .translationYBy(contentDisplacement)
159 | .setDuration(duration)
160 | .withStartAction {
161 | currState = STATE.PRESSING
162 | }
163 | .withEndAction {
164 | currState = STATE.PRESSED
165 |
166 | if (animUpUnconsumed)
167 | animateUp()
168 | }.start()
169 |
170 | sideShadow.animate().translationXBy(-shadowDisplacement).translationYBy(-shadowDisplacement)
171 | .setDuration(duration)
172 | .start()
173 |
174 | bottomShadow.animate().translationYBy(-shadowDisplacement)
175 | .translationXBy(-shadowDisplacement)
176 | .setDuration(duration).start()
177 | }
178 |
179 | private fun animateUp() {
180 | contentContainer.animate().translationYBy(-(contentDisplacement))
181 | .translationXBy(-(contentDisplacement))
182 | .setDuration(duration)
183 | .withStartAction {
184 | currState = STATE.RELEASING
185 | }
186 | .withEndAction {
187 | currState = STATE.RELEASED
188 | animUpUnconsumed = false
189 | }
190 | .start()
191 |
192 | sideShadow.animate().translationXBy(shadowDisplacement)
193 | .translationYBy(shadowDisplacement).setDuration(duration)
194 | .start()
195 |
196 | bottomShadow.animate().translationYBy(shadowDisplacement)
197 | .translationXBy(shadowDisplacement)
198 | .setDuration(duration).start()
199 | }
200 |
201 | private val Number.toPx
202 | get() = TypedValue.applyDimension(
203 | TypedValue.COMPLEX_UNIT_DIP,
204 | this.toFloat(),
205 | Resources.getSystem().displayMetrics
206 | )
207 |
208 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/ui/retroDesign/RetroLowElevationLayout.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.ui.retroDesign
2 |
3 | import android.content.Context
4 | import android.content.res.Resources
5 | import android.util.AttributeSet
6 | import android.util.TypedValue
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import androidx.cardview.widget.CardView
10 | import androidx.constraintlayout.widget.ConstraintLayout
11 | import com.example.androidconcepts.R
12 |
13 | class RetroLowElevationLayout constructor(
14 | context: Context,
15 | attributeSet: AttributeSet?
16 | ) : CardView(context, attributeSet) {
17 |
18 | private val root: View
19 | private val contentContainer: CardView
20 |
21 | init {
22 | root = LayoutInflater.from(context).inflate(R.layout.layout_retro_low_elev, this, true)
23 | contentContainer = root.findViewById(R.id.cv_content)
24 |
25 | cardElevation = 0f
26 | }
27 |
28 | override fun onAttachedToWindow() {
29 | super.onAttachedToWindow()
30 |
31 | if (contentContainer.childCount > 0) return // assuming the target view is already added.
32 |
33 | if (getChildAt(1) != null) {
34 |
35 | // detach user's target view from current pos in hierarchy and attach to content container.
36 | // child at 0th pos is "root".
37 |
38 | val view = getChildAt(1)
39 | removeView(view)
40 | (contentContainer).addView(view)
41 | }
42 |
43 | val isWidthMatchParent =
44 | layoutParams.width == android.view.ViewGroup.LayoutParams.MATCH_PARENT
45 |
46 | if (isWidthMatchParent) {
47 | // if match parent then we need to modify some attributes.
48 | getChildAt(0).layoutParams.width = android.view.ViewGroup.LayoutParams.MATCH_PARENT
49 | (contentContainer.layoutParams as ConstraintLayout.LayoutParams).setMargins(
50 | 0,
51 | 0,
52 | resources.getDimension(R.dimen.neopop_def_space).toInt() - 5,
53 | 0
54 | )
55 | contentContainer.layoutParams.width = android.view.ViewGroup.LayoutParams.MATCH_PARENT
56 | invalidate()
57 | }
58 |
59 | }
60 |
61 | private val Number.toPx
62 | get() = TypedValue.applyDimension(
63 | TypedValue.COMPLEX_UNIT_DIP,
64 | this.toFloat(),
65 | Resources.getSystem().displayMetrics
66 | )
67 |
68 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/androidconcepts/ui/retroDesign/RetroShimmerView.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.ui.retroDesign
2 |
3 | import android.animation.ValueAnimator
4 | import android.content.Context
5 | import android.graphics.Canvas
6 | import android.graphics.Color
7 | import android.graphics.Paint
8 | import android.graphics.Rect
9 | import android.graphics.RectF
10 | import android.util.Log
11 | import android.view.View
12 | import android.view.ViewGroup
13 | import android.view.animation.AccelerateDecelerateInterpolator
14 | import android.widget.LinearLayout
15 | import androidx.core.animation.doOnEnd
16 |
17 | class RetroShimmerView constructor(
18 | context: Context
19 | ) : View(context) {
20 |
21 | private var dx = 0f
22 | private var shimmerWidth = 0f
23 | private var isAnimInProgress = false
24 |
25 | fun startAnim(widthProvided: Float) {
26 |
27 | if (isAnimInProgress) return
28 |
29 | isAnimInProgress = true
30 | shimmerWidth = widthProvided / 10 // 10 % of view
31 |
32 | dx = 0f
33 |
34 | ValueAnimator.ofFloat(0f - shimmerWidth, widthProvided).setDuration(1000L).apply {
35 |
36 | this.interpolator = AccelerateDecelerateInterpolator()
37 |
38 | this.addUpdateListener {
39 | dx = it.animatedValue as Float
40 | invalidate()
41 | }
42 |
43 | this.doOnEnd {
44 | isAnimInProgress = false
45 | }
46 |
47 | this.start()
48 | }
49 | }
50 |
51 | private val rectF = RectF(0f, 0f, 0f, 0f)
52 | private val paint = Paint().apply {
53 | color = Color.WHITE
54 | }
55 |
56 | override fun onDraw(canvas: Canvas?) {
57 | super.onDraw(canvas)
58 |
59 | rectF.left = left + dx
60 | rectF.top = top.toFloat()
61 | rectF.right = left + dx + shimmerWidth
62 | rectF.bottom = top + height.toFloat()
63 |
64 | canvas?.drawRect(rectF, paint)
65 | }
66 |
67 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/border.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_arrow_circle_right_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_star_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/activity_manual_config1.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/activity_manual_config2.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/fragment_manual_config_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/fragment_manual_config_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_coroutine_cancel.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_coroutine_learning2.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_manual_config1.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_manual_config2.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_rx_cancel.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_manual_config_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_manual_config_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_retro_default.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
19 |
20 |
21 |
22 |
23 |
24 |
34 |
35 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_retro_low_elev.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidsharma2002/AndroidConcepts/e1f45c1e2614fe5b7961f0bbb37989196dcb40de/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidsharma2002/AndroidConcepts/e1f45c1e2614fe5b7961f0bbb37989196dcb40de/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidsharma2002/AndroidConcepts/e1f45c1e2614fe5b7961f0bbb37989196dcb40de/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidsharma2002/AndroidConcepts/e1f45c1e2614fe5b7961f0bbb37989196dcb40de/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidsharma2002/AndroidConcepts/e1f45c1e2614fe5b7961f0bbb37989196dcb40de/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidsharma2002/AndroidConcepts/e1f45c1e2614fe5b7961f0bbb37989196dcb40de/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidsharma2002/AndroidConcepts/e1f45c1e2614fe5b7961f0bbb37989196dcb40de/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidsharma2002/AndroidConcepts/e1f45c1e2614fe5b7961f0bbb37989196dcb40de/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidsharma2002/AndroidConcepts/e1f45c1e2614fe5b7961f0bbb37989196dcb40de/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidsharma2002/AndroidConcepts/e1f45c1e2614fe5b7961f0bbb37989196dcb40de/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 6dp
4 | 2dp
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AndroidConcepts
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/advancedConcurrency/fileDownloader/FileDownloadUseCaseTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.fileDownloader
2 |
3 | import com.example.androidconcepts.common.BgThreadPoster
4 | import com.example.androidconcepts.common.UiThreadPosterTD
5 | import io.mockk.mockk
6 | import io.mockk.verify
7 | import org.junit.Before
8 | import org.junit.Test
9 | import java.util.concurrent.CountDownLatch
10 | import java.util.concurrent.Semaphore
11 | import java.util.concurrent.TimeUnit
12 |
13 | class FileDownloadUseCaseTest {
14 |
15 | private var downloadedFile: DownloadedFile? = null
16 | private val noopListener = object : FileDownloadUseCase.Listener {
17 | override fun onFileResult(downloadedFile: DownloadedFile) {
18 | /* no-op */
19 | }
20 | }
21 |
22 | @Test
23 | fun onSubmittingSameFileTwiceSync_downloadsOnlyOnce() {
24 | // arrange
25 | val file = File("file1", "some url1")
26 |
27 | // act
28 | SUT.startDownloadFileAsync(file, noopListener).get()
29 | SUT.startDownloadFileAsync(file, noopListener).get()
30 |
31 | // assert
32 | verify(exactly = 1) {
33 | fileDownloader.downloadFileSync(file)
34 | }
35 | }
36 |
37 | @Test
38 | fun onSubmittingSameFileThrice_downloadsOnlyOnce() {
39 | // arrange
40 | val file = File("file1", "some url1")
41 | val semaphore = Semaphore(-2)
42 |
43 | // act
44 | repeat(3) {
45 | Thread {
46 | SUT.startDownloadFileAsync(file, object : FileDownloadUseCase.Listener {
47 | override fun onFileResult(downloadedFile: DownloadedFile) {
48 | semaphore.release()
49 | }
50 | })
51 | }.start()
52 | }
53 |
54 | semaphore.acquire()
55 |
56 | // assert
57 | verify(exactly = 1) {
58 | fileDownloader.downloadFileSync(file)
59 | }
60 | }
61 |
62 | @Test
63 | fun performanceTestWhenDownloadingFiveDifferentFiles() {
64 | // arrange
65 | val semaphore = Semaphore(-4)
66 |
67 | // act
68 | repeat(5) {
69 | Thread {
70 | val file = File("file$it", "some url$it")
71 |
72 | SUT.startDownloadFileAsync(file, object : FileDownloadUseCase.Listener {
73 | override fun onFileResult(downloadedFile: DownloadedFile) {
74 | semaphore.release()
75 | }
76 | })
77 | }.start()
78 | }
79 |
80 | semaphore.acquire() // should be less than 2.5 secs (500 * 5)
81 | }
82 |
83 | private lateinit var SUT: FileDownloadUseCase
84 | private lateinit var fileDownloader: FileDownloaderTD
85 |
86 | @Before
87 | fun setup() {
88 | downloadedFile = null
89 | val bgThreadPoster = BgThreadPoster()
90 | val uiThreadPoster = UiThreadPosterTD()
91 | fileDownloader = mockk(relaxed = true)
92 | SUT = FileDownloadUseCase(bgThreadPoster, uiThreadPoster, fileDownloader)
93 | }
94 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/advancedConcurrency/fileDownloader/FileDownloaderTD.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.fileDownloader
2 |
3 | import com.example.androidconcepts.common.BgThreadPoster
4 | import com.example.androidconcepts.common.UiThreadPosterTD
5 |
6 | class FileDownloaderTD : FileDownloader {
7 | override fun downloadFileSync(file: File): FileDownloader.Result {
8 | Thread.sleep(500L)
9 | return FileDownloader.Result.Success(DownloadedFile(file.name, file.url, listOf()))
10 | }
11 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/advancedConcurrency/producerConsumer/ProducerConsumerTest.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.advancedConcurrency.producerConsumer;
2 |
3 | import com.example.androidconcepts.advancedConcurrency.producerConsumer.example1.WrongProducerConsumerExample1;
4 | import com.example.androidconcepts.advancedConcurrency.producerConsumer.example2.CorrectProducerConsumerExample2;
5 | import com.example.androidconcepts.advancedConcurrency.producerConsumer.example3.BQProducerConsumerExample3;
6 | import com.example.androidconcepts.advancedConcurrency.producerConsumer.example4.BQDishRackExample4;
7 | import com.example.androidconcepts.advancedConcurrency.producerConsumer.example5.BQDishRackExample5;
8 |
9 | import org.junit.Test;
10 |
11 | public class ProducerConsumerTest {
12 |
13 | @Test
14 | public void wrongProducerConsumerTest() throws Exception {
15 | WrongProducerConsumerExample1 SUT = new WrongProducerConsumerExample1();
16 | SUT.startExecution();
17 | }
18 |
19 | @Test
20 | public void correctProducerConsumerTest() {
21 | CorrectProducerConsumerExample2 SUT = new CorrectProducerConsumerExample2();
22 | SUT.startExecution();
23 | }
24 |
25 | @Test
26 | public void BlockingQueueProducerConsumerTest() {
27 | BQProducerConsumerExample3 SUT = new BQProducerConsumerExample3();
28 | SUT.startExecution();
29 | }
30 |
31 | @Test
32 | public void BQDishRackExample4Test() {
33 | BQDishRackExample4 SUT = new BQDishRackExample4();
34 | SUT.startExecution();
35 | }
36 |
37 | @Test
38 | public void BQDishRackExample5Test() {
39 | BQDishRackExample5 SUT = new BQDishRackExample5();
40 | SUT.startExecution();
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/common/Benchmark.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.common
2 |
3 | fun measureTime(action: () -> Unit): Long {
4 | val startTime = System.currentTimeMillis()
5 | action.invoke()
6 | val endTime = System.currentTimeMillis()
7 | val measuredTime = endTime - startTime
8 | println("measure time = " + measuredTime + "ms")
9 | return measuredTime
10 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/common/BgThreadPosterTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.common
2 |
3 | import org.junit.Assert.*
4 | import org.junit.Before
5 | import org.junit.Test
6 | import java.util.concurrent.CountDownLatch
7 | import java.util.concurrent.ExecutorService
8 | import java.util.concurrent.Executors
9 | import java.util.concurrent.Future
10 | import java.util.concurrent.TimeUnit
11 |
12 | class BgThreadPosterTest {
13 |
14 | private lateinit var SUT: BgThreadPoster
15 | private var mainThreadName: String = ""
16 |
17 | @Before
18 | fun setup() {
19 | SUT = BgThreadPoster()
20 | mainThreadName = Thread.currentThread().name
21 | }
22 |
23 | @Test
24 | @Throws(InterruptedException::class)
25 | fun postedRunnableExecutesSuccessfullyOnBackgroundThread() {
26 | // arrange
27 | var ran = false
28 | var isBgThread = true
29 | val countDownLatch = CountDownLatch(1)
30 |
31 | val runnable = Runnable {
32 | ran = true
33 | isBgThread = !(Thread.currentThread().name.equals(mainThreadName))
34 | countDownLatch.countDown()
35 | }
36 |
37 | // act
38 | SUT.post(runnable)
39 |
40 | // assert
41 | countDownLatch.await(1, TimeUnit.SECONDS)
42 | assert(ran)
43 | assert(isBgThread)
44 | }
45 |
46 | @Test
47 | fun postingNRunnablesCreateNThreads() {
48 | // arrange
49 | val jobs = mutableListOf>()
50 |
51 | // act
52 | repeat(20) {
53 | jobs += SUT.post {
54 | Thread.sleep(500L)
55 | println("i : $it + thread name : " + Thread.currentThread().name)
56 | }
57 | }
58 |
59 | // assert
60 | jobs.forEach {
61 | it.get()
62 | }
63 | }
64 |
65 | @Test
66 | fun deadlockOnSingleThreadedExecutorTest() {
67 | // arrange
68 | val SUT1 = BgThreadPosterSingleThreaded()
69 |
70 | // act
71 | SUT1.post {
72 | println("posted runnable1 started " + Thread.currentThread().name)
73 |
74 | SUT1.post {
75 | println("posted runnable2 " + Thread.currentThread().name)
76 | }.get() // deadlock
77 |
78 | println("posted runnable1 ended " + Thread.currentThread().name)
79 | }.get()
80 | }
81 |
82 | @Test
83 | fun deadlockOnDualThreadedExecutorTest() {
84 | // arrange
85 | val SUT1 = BgThreadPosterDualThreaded()
86 |
87 | // act
88 | SUT1.post {
89 | println("posted runnable1 started " + Thread.currentThread().name) // thread-1
90 |
91 | SUT1.post {
92 | println("posted runnable2 started " + Thread.currentThread().name) // thread2
93 |
94 | SUT1.post {
95 | println("posted runnable3 started " + Thread.currentThread().name)
96 | }.get() // deadlocked at this as thread1 and thread2 are occupied and blocked.
97 |
98 | println("posted runnable2 ended " + Thread.currentThread().name)
99 | }.get()
100 |
101 | println("posted runnable1 ended " + Thread.currentThread().name)
102 | }.get()
103 |
104 | // Explanation : here t2 waits for runnable3 to complete on t1 but t1 waits for runnable2 to complete on t2.
105 | // see figure ->
106 | // t1 : [runnable1 = line89, [post runnable2], [get() on runnable2], line107] , [runnable3]
107 | // t2 : [runnable2 = line92, [post runnable3], [get() on runnable3], line104]
108 | }
109 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/common/UiThreadPosterTD.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.common
2 |
3 | import android.os.Handler
4 | import java.util.concurrent.CountDownLatch
5 | import java.util.concurrent.Executors
6 | import java.util.concurrent.TimeUnit
7 |
8 | class UiThreadPosterTD : UiThreadPoster() {
9 |
10 | private val taskExecutor = Executors.newSingleThreadExecutor()
11 |
12 | override fun getUiHandler(): Handler? {
13 | return null
14 | }
15 |
16 | override fun post(runnable: Runnable) {
17 | taskExecutor.submit(runnable)
18 | }
19 |
20 | override fun postDelayed(delayTime: Long, runnable: Runnable) {
21 | taskExecutor.submit {
22 | Thread.sleep(delayTime)
23 | runnable.run()
24 | }
25 | }
26 |
27 | /**
28 | * NOTE : can cause deadlock if submitted to "this".
29 | * eg : uiThreadPoster.post {
30 | * uiThreadPoster.postDelayedAndWait(...)
31 | * }
32 | */
33 | override fun postDelayedAndWait(delayTime: Long, awaitTime: Long, runnable: Runnable) {
34 | val latch = CountDownLatch(1)
35 |
36 | taskExecutor.submit {
37 | Thread.sleep(delayTime)
38 | runnable.run()
39 | latch.countDown()
40 | }
41 |
42 | latch.await(awaitTime, TimeUnit.SECONDS)
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/coroutines/learning1/FibonacciUseCaseCallbackTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.coroutines.learning1
2 |
3 | import android.os.Handler
4 | import com.example.androidconcepts.common.UiThreadPoster
5 | import com.example.androidconcepts.common.UiThreadPosterTD
6 | import io.mockk.mockk
7 | import org.junit.Assert.*
8 | import org.junit.Before
9 | import org.junit.Test
10 | import java.util.concurrent.CountDownLatch
11 | import java.util.concurrent.TimeUnit
12 |
13 | class FibonacciUseCaseCallbackTest {
14 |
15 | private lateinit var SUT: FibonacciUseCaseUsingCallback
16 |
17 | @Test
18 | @Throws(InterruptedException::class)
19 | fun fibonacciNoOf10is55() {
20 | // arrange
21 | val countDownLatch = CountDownLatch(1)
22 | var result: Int = -1
23 |
24 | // act
25 | SUT.fetchFibonacciNumberAsync(10, object : FibonacciUseCaseUsingCallback.Callback {
26 | override fun onResult(fibonacciNumber: Int) {
27 | result = fibonacciNumber
28 | countDownLatch.countDown()
29 | }
30 | })
31 |
32 | // assert
33 | countDownLatch.await(1, TimeUnit.SECONDS)
34 | assert(result == 55)
35 | }
36 |
37 | @Before
38 | fun setup() {
39 | SUT = FibonacciUseCaseUsingCallback(uiThreadPoster = UiThreadPosterTD())
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/coroutines/learning1/FibonacciUseCaseUsingCoroutineTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.coroutines.learning1
2 |
3 | import kotlinx.coroutines.runBlocking
4 | import org.junit.Assert.*
5 |
6 | import org.junit.Before
7 | import org.junit.Test
8 |
9 | class FibonacciUseCaseUsingCoroutineTest {
10 |
11 | private lateinit var SUT: FibonacciUseCaseUsingCoroutine
12 |
13 | @Test
14 | fun fibonacciNoOf10is55() = runBlocking {
15 | // act
16 | val result = SUT.getFibonacciNumber(10)
17 |
18 | // assert
19 | assert(result == 55)
20 | }
21 |
22 | @Before
23 | fun setUp() {
24 | SUT = FibonacciUseCaseUsingCoroutine()
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/coroutines/performance/AddImageFiltersUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.coroutines.performance
2 |
3 | class AddImageFiltersUseCase {
4 | suspend fun addFilters() {
5 | println("adding image filters started")
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/coroutines/performance/CoroutinePerformanceTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.coroutines.performance
2 |
3 | import kotlinx.coroutines.*
4 | import org.junit.Test
5 | import java.util.concurrent.CountDownLatch
6 | import java.util.concurrent.Executors
7 | import java.util.concurrent.TimeUnit
8 |
9 | class CoroutinePerformanceTest {
10 |
11 | @Test
12 | fun performanceTestOfDefaultDispatcher() = runBlocking {
13 |
14 | val processImageUseCase = ProcessImageUseCase()
15 | val addImageFiltersUseCase = AddImageFiltersUseCase()
16 | val coroutineScope = CoroutineScope(Dispatchers.Default)
17 | val noOfIterations = Runtime.getRuntime().availableProcessors() // to saturate default dispatcher
18 | val eachTaskTime = 5000
19 | val allTasksPostedCDL = CountDownLatch(noOfIterations)
20 | val startTime = System.currentTimeMillis()
21 |
22 | println(noOfIterations)
23 |
24 | val backgroundJob = coroutineScope.launch {
25 | // simulate background processing of images
26 | repeat(noOfIterations) {
27 | launch {
28 | println("task1 " + Thread.currentThread().id)
29 | processImageUseCase.process(eachTaskTime)
30 | }
31 |
32 | allTasksPostedCDL.countDown()
33 | }
34 | }
35 |
36 | println("posted all tasks in ${System.currentTimeMillis() - startTime} ms")
37 | allTasksPostedCDL.await()
38 |
39 | // simulate user action
40 | val userJob = coroutineScope.launch {
41 | println("task2 " + Thread.currentThread().id)
42 | addImageFiltersUseCase.addFilters()
43 | }
44 |
45 | backgroundJob.invokeOnCompletion {
46 | println("background processing completed in: ${System.currentTimeMillis() - startTime} ms")
47 | }
48 |
49 | userJob.invokeOnCompletion {
50 | println("user action latency: ${System.currentTimeMillis() - startTime} ms")
51 | }
52 |
53 | joinAll(backgroundJob, userJob)
54 | }
55 |
56 | @Test
57 | fun performanceTestOfSingleThreadedDispatcher() = runBlocking {
58 | val processImageUseCase = ProcessImageUseCase()
59 | val addImageFiltersUseCase = AddImageFiltersUseCase()
60 |
61 | val boundedDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher()
62 | val coroutineScope = CoroutineScope(boundedDispatcher)
63 | val noOfIterations = Runtime.getRuntime().availableProcessors() // to saturate default dispatcher
64 | val eachTaskTime = 2000
65 | val allTasksPostedCDL = CountDownLatch(noOfIterations)
66 | val startTime = System.currentTimeMillis()
67 |
68 | println(noOfIterations)
69 |
70 | val backgroundJob = coroutineScope.launch {
71 | // simulate background processing of images
72 | repeat(noOfIterations) {
73 | launch {
74 | println("task1 " + Thread.currentThread().id)
75 | processImageUseCase.process(eachTaskTime)
76 | }
77 |
78 | allTasksPostedCDL.countDown()
79 | }
80 | }
81 |
82 | println("posted all tasks in ${System.currentTimeMillis() - startTime} ms")
83 | allTasksPostedCDL.await()
84 |
85 | // simulate user action
86 | val userJob = coroutineScope.launch {
87 | println("task2 " + Thread.currentThread().id)
88 | addImageFiltersUseCase.addFilters()
89 | }
90 |
91 | backgroundJob.invokeOnCompletion {
92 | println("background processing completed in: ${System.currentTimeMillis() - startTime} ms")
93 | }
94 |
95 | userJob.invokeOnCompletion {
96 | println("user action latency: ${System.currentTimeMillis() - startTime} ms")
97 | }
98 |
99 | joinAll(backgroundJob, userJob)
100 | }
101 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/coroutines/performance/ProcessImageUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.coroutines.performance
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.withContext
5 |
6 | class ProcessImageUseCase {
7 | suspend fun process(time: Int) {
8 | // simulate algorithm invocation for time ms in a "blocking manner".
9 | // (fake load just to avoid elimination optimization by compilers)
10 | val finishTimeMillis = System.currentTimeMillis() + time
11 | var counter = 0
12 | while (System.currentTimeMillis() < finishTimeMillis) {
13 | counter++
14 | }
15 | if (counter > 250000000) {
16 | println("wow, your device has really fast cores")
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/jcip/chapter2_threadSafety/atomicSyncBug/AtomicServerTest.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter2_threadSafety.atomicSyncBug;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.concurrent.CountDownLatch;
6 | import java.util.concurrent.TimeUnit;
7 |
8 | public class AtomicServerTest {
9 |
10 | @Test
11 | public void performConcurrencyTestOfWrongServer() throws Exception {
12 | // arrange
13 | WrongAtomicServer SUT = new WrongAtomicServer();
14 | CountDownLatch countDownLatch = new CountDownLatch(100);
15 |
16 | // act
17 | for (int i = 0; i < 100; i++) {
18 | new Thread(() -> {
19 | SUT.performCalculation();
20 | countDownLatch.countDown();
21 | }).start();
22 | }
23 |
24 | countDownLatch.await(10, TimeUnit.SECONDS);
25 |
26 | // assert
27 | Long hits = SUT.getHits();
28 |
29 | if (hits > 10) {
30 | throw new Exception("hits read are " + hits);
31 | }
32 | }
33 |
34 | @Test
35 | public void performConcurrencyTestOfCorrectServer() throws Exception {
36 | // arrange
37 | CorrectAtomicServer SUT = new CorrectAtomicServer();
38 | CountDownLatch countDownLatch = new CountDownLatch(100);
39 |
40 | // act
41 | for (int i = 0; i < 100; i++) {
42 | new Thread(() -> {
43 | SUT.performCalculation();
44 | countDownLatch.countDown();
45 | }).start();
46 | }
47 |
48 | countDownLatch.await(10, TimeUnit.SECONDS);
49 |
50 | // assert
51 | Long hits = SUT.getHits();
52 |
53 | if (hits > 10) {
54 | throw new Exception("hits read are " + hits);
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/jcip/chapter2_threadSafety/intrinsicLocks/HomeWidgetTest.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter2_threadSafety.intrinsicLocks;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.concurrent.CountDownLatch;
6 | import java.util.concurrent.TimeUnit;
7 |
8 | public class HomeWidgetTest {
9 |
10 | @Test
11 | public void performTaskTest() {
12 | // arrange
13 | CountDownLatch countDownLatch = new CountDownLatch(1);
14 | HomeWidget homeWidget = new HomeWidget();
15 |
16 | // act
17 | new Thread(() -> {
18 | try {
19 | // assert, that homeWidget doesn't run for more than 1 second.
20 | boolean hasFinished = countDownLatch.await(1, TimeUnit.SECONDS);
21 | assert hasFinished;
22 | } catch (InterruptedException e) {
23 | e.printStackTrace();
24 | }
25 | }).start();
26 |
27 | homeWidget.performTask();
28 | countDownLatch.countDown();
29 | }
30 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/jcip/chapter2_threadSafety/sync/SyncServerTest.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter2_threadSafety.sync;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import org.junit.Test;
6 |
7 | import java.util.concurrent.CountDownLatch;
8 | import java.util.concurrent.TimeUnit;
9 |
10 | public class SyncServerTest {
11 |
12 | @Test
13 | public void testingSync() throws Exception {
14 | SyncServer server = new SyncServer();
15 | CountDownLatch latch = new CountDownLatch(2);
16 |
17 | new Thread(() -> {
18 | server.perform1();
19 | }).start();
20 |
21 | // wait for perform1 thread to start
22 | Thread.sleep(100);
23 |
24 | new Thread(() -> {
25 | server.perform2();
26 | latch.countDown();
27 | }).start();
28 |
29 | new Thread(() -> {
30 | server.perform3();
31 | latch.countDown();
32 | }).start();
33 |
34 | if (latch.await(3, TimeUnit.SECONDS)) {
35 | throw new Exception("perform2 and perform3 executed!");
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/jcip/chapter2_threadSafety/syncPerfIssue/ServerTest.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter2_threadSafety.syncPerfIssue;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.concurrent.CountDownLatch;
6 |
7 | public class ServerTest {
8 |
9 | @Test
10 | public void slowServerPerfTest() throws InterruptedException {
11 | SlowServer SUT = new SlowServer();
12 | CountDownLatch latch = new CountDownLatch(20);
13 |
14 | for (int i = 0; i < 20; i++) {
15 | new Thread(() -> {
16 | SUT.performCalculations();
17 | latch.countDown();
18 | }).start();
19 | }
20 |
21 | latch.await(); // completes in 20 * 100 = 2 secs
22 | }
23 |
24 | @Test
25 | public void fastServerPerfTest() throws InterruptedException {
26 | FastServer SUT = new FastServer();
27 | CountDownLatch latch = new CountDownLatch(20);
28 |
29 | for (int i = 0; i < 20; i++) {
30 | new Thread(() -> {
31 | SUT.performCalculations();
32 | latch.countDown();
33 | }).start();
34 | }
35 |
36 | latch.await(); // completes in 134 ms
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/jcip/chapter3_sharingObjects/escape/EscapeExampleTest.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter3_sharingObjects.escape;
2 |
3 | import org.junit.Test;
4 |
5 | public class EscapeExampleTest {
6 |
7 | @Test
8 | public void wrongEscapeExampleTest() {
9 |
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/jcip/chapter3_sharingObjects/visibility/VisibilityExampleTest.java:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcip.chapter3_sharingObjects.visibility;
2 |
3 | import org.junit.Test;
4 |
5 | public class VisibilityExampleTest {
6 |
7 | @Test
8 | public void noVisibilityExampleTest() {
9 | NoVisibilityExample SUT = new NoVisibilityExample();
10 | SUT.startExecution();
11 | }
12 |
13 | @Test
14 | public void correctVisibilityExampleTest() {
15 | NoVisibilityExample2 SUT = new NoVisibilityExample2();
16 | SUT.startExecution();
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/jcr/collections/CollectionPerformanceTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcr.collections
2 |
3 | import com.example.androidconcepts.common.measureTime
4 | import org.junit.Test
5 | import java.util.LinkedList
6 | import java.util.TreeSet
7 |
8 | /**
9 | * important link : https://www.geeksforgeeks.org/hashset-vs-treeset-in-java/
10 | */
11 | class CollectionPerformanceTest {
12 |
13 | private val tenPower6 = 1000000 // gives out of java heap for greater than this
14 |
15 | @Test
16 | fun arrayListTest() {
17 | val arrayList = mutableListOf()
18 | measureTime {
19 | repeat(tenPower6) {
20 | arrayList.add(it)
21 | } // 20ms
22 | }
23 |
24 | measureTime {
25 | repeat(1000) {
26 | arrayList.add(arrayList.size / 2, it)
27 | } // 80ms
28 | }
29 |
30 | measureTime {
31 | repeat(1000) {
32 | arrayList.add(0, it)
33 | } // 300ms
34 | }
35 | }
36 |
37 | @Test
38 | fun linkedListTest() {
39 | val linkedList = LinkedList()
40 |
41 | measureTime {
42 | repeat(tenPower6) { // 10^6
43 | linkedList.add(it)
44 | } // 120ms
45 | }
46 |
47 | measureTime {
48 | repeat(1000) {
49 | linkedList.add(linkedList.size / 2, it)
50 | } // 1sec
51 | }
52 |
53 | measureTime {
54 | repeat(1000) {
55 | linkedList.add(0, it)
56 | } // 10ms
57 | }
58 | }
59 |
60 | @Test
61 | fun hashsetTest() {
62 | val hashset = HashSet() // unique
63 |
64 | measureTime {
65 | repeat(tenPower6) { // 10^6
66 | hashset.add(it)
67 | } // 85ms
68 | }
69 |
70 | measureTime {
71 | repeat(tenPower6) {
72 | hashset.contains(it)
73 | } // 50ms
74 | }
75 | }
76 |
77 | @Test
78 | fun linkedHashSetTest() {
79 | val linkedHashSet = java.util.LinkedHashSet() // unique
80 |
81 | measureTime {
82 | repeat(tenPower6) {
83 | linkedHashSet.add(it)
84 | } // 100ms
85 | }
86 |
87 | measureTime {
88 | repeat(tenPower6) {
89 | linkedHashSet.contains(it)
90 | } // 180ms
91 | }
92 | }
93 |
94 | @Test
95 | fun treeSetTest() {
96 | val treeSet = TreeSet() // sorted and unique
97 |
98 | measureTime {
99 | repeat(tenPower6) {
100 | treeSet.add(it)
101 | } // 150ms
102 | }
103 | }
104 |
105 | @Test
106 | fun hashmapTest() {
107 | val hashmap = HashMap()
108 |
109 | measureTime {
110 | repeat(tenPower6) {
111 | hashmap[it] = it.toString()
112 | } // 150ms
113 | }
114 |
115 | measureTime {
116 | repeat(tenPower6) {
117 | hashmap[it]
118 | } // 30ms
119 | }
120 | }
121 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/jcr/collections/CollectionsInterfaceTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcr.collections
2 |
3 | import org.junit.Test
4 |
5 | class CollectionsInterfaceTest {
6 |
7 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/jcr/collections/ListInterfaceTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcr.collections
2 |
3 | import com.example.androidconcepts.jcr.collections.listInterface.ListInterfaceExample1
4 | import org.junit.Test
5 |
6 | class ListInterfaceTest {
7 |
8 | private val SUT = ListInterfaceExample1()
9 |
10 | @Test
11 | fun mutableListTest() {
12 | SUT.stringList_ArrayList.add("3")
13 | }
14 |
15 | @Test
16 | fun immutableListTest() {
17 | try {
18 | SUT.stringList_UnmodifiableList.add("4")
19 | } catch (e: java.lang.Exception) {
20 | assert(e is UnsupportedOperationException)
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/jcr/collections/SetInterfaceTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.jcr.collections
2 |
3 | import com.example.androidconcepts.jcr.collections.setInterface.SetInterfaceExample1
4 | import org.junit.Test
5 |
6 | class SetInterfaceTest {
7 |
8 | private val SUT = SetInterfaceExample1()
9 |
10 | @Test
11 | fun stringSetAlphaNumericInterfaceTest() {
12 | SUT.stringSetAlphaNumeric.forEach {
13 | println("set $it")
14 | }
15 |
16 | // prints : 1b,2c,4e,3a,5d
17 | }
18 |
19 | @Test
20 | fun stringSetNumericInterfaceTest() {
21 | SUT.stringSetNumeric.forEach {
22 | println("set $it")
23 | }
24 |
25 | // prints : 1,2,3,4,5
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/livedata/learning2/ObservableDataHolderTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.livedata.learning2
2 |
3 | import com.example.androidconcepts.common.UiThreadPosterTD
4 | import com.example.androidconcepts.livedata.learning2.ObservableDataHolder
5 | import org.junit.Before
6 | import org.junit.Test
7 | import java.util.concurrent.CountDownLatch
8 | import java.util.concurrent.TimeUnit
9 |
10 | class ObservableDataHolderTest {
11 |
12 | private lateinit var SUT: ObservableDataHolder
13 | private lateinit var uiThreadPosterTD: UiThreadPosterTD
14 |
15 | @Test
16 | fun observerRegistered_valueIsSet_valueGetsObserved() {
17 |
18 | var observedData = -1
19 | val latch = CountDownLatch(1)
20 |
21 | uiThreadPosterTD.post {
22 | SUT.registerListener(object : ObservableDataHolder.Observer {
23 | override fun onValueChanged(data: Int) {
24 | observedData = data
25 | latch.countDown()
26 | }
27 | })
28 |
29 | SUT.setData(1)
30 | }
31 |
32 | assert(latch.await(1, TimeUnit.SECONDS))
33 | assert(observedData == 1)
34 | }
35 |
36 | @Test
37 | fun noValueIsSet_observerDoesNotObserveAnyValue() {
38 |
39 | val latch = CountDownLatch(1)
40 |
41 | uiThreadPosterTD.post {
42 | SUT.registerListener(object : ObservableDataHolder.Observer {
43 | override fun onValueChanged(data: Int) {
44 | latch.countDown()
45 | }
46 | })
47 | }
48 |
49 | assert(!latch.await(100, TimeUnit.MILLISECONDS))
50 | }
51 |
52 | @Test
53 | fun observerRegistered_multipleValuesAreSet_valuesGetObservedCorrectly() {
54 |
55 | val observedValues: MutableList = mutableListOf()
56 | val latch = CountDownLatch(5)
57 |
58 | uiThreadPosterTD.post {
59 | SUT.registerListener(object : ObservableDataHolder.Observer {
60 | override fun onValueChanged(data: Int) {
61 | observedValues.add(data)
62 | latch.countDown()
63 | }
64 | })
65 |
66 | SUT.setData(1)
67 | SUT.setData(2)
68 | SUT.setData(3)
69 | SUT.setData(4)
70 | SUT.setData(5)
71 | }
72 |
73 | latch.await(1, TimeUnit.SECONDS)
74 |
75 | val expectedList: List = List(5) {
76 | it + 1 // start from 1
77 | }
78 |
79 | assert(observedValues == expectedList)
80 | }
81 |
82 | @Test
83 | fun longRunningObservers_doesNotBlockSetValue() {
84 |
85 | val latch = CountDownLatch(100)
86 |
87 | uiThreadPosterTD.post {
88 |
89 | registerLongRunningObservers(noOfObservers = 100)
90 |
91 | repeat(100) {
92 | Thread {
93 | SUT.setData(it)
94 | latch.countDown()
95 | }.start()
96 | }
97 | }
98 |
99 | assert(latch.await(50, TimeUnit.MILLISECONDS))
100 | }
101 |
102 | private fun registerLongRunningObservers(noOfObservers: Int) {
103 | repeat(noOfObservers) {
104 | SUT.registerListener(object : ObservableDataHolder.Observer {
105 | override fun onValueChanged(data: Int) {
106 | Thread.sleep(100L) // sleep for straight n * 100 millis
107 | }
108 | })
109 | }
110 | }
111 |
112 | @Test
113 | fun dataSetAfterObserverIsUnregistered_notifiesOfMissedNotification() {
114 |
115 | var observedData: Int? = null
116 | val latch = CountDownLatch(2)
117 |
118 | uiThreadPosterTD.post {
119 |
120 | val observer = object : ObservableDataHolder.Observer {
121 | override fun onValueChanged(data: Int) {
122 | observedData = data
123 | latch.countDown()
124 | }
125 | }
126 |
127 | // onStart
128 | SUT.registerListener(observer)
129 |
130 | // onStop
131 | SUT.unregisterListener(observer)
132 |
133 | // data is fetched from api and set to dataHolder.
134 | SUT.setData(1)
135 |
136 | // onStart
137 | SUT.registerListener(observer)
138 | }
139 |
140 | assert(latch.await(1, TimeUnit.SECONDS))
141 | assert(observedData == 1)
142 | }
143 |
144 | @Before
145 | fun setup() {
146 | uiThreadPosterTD = UiThreadPosterTD()
147 | SUT = ObservableDataHolder(uiThreadPoster = uiThreadPosterTD)
148 | }
149 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/androidconcepts/rxJava/ownImpl/ReactObservableUsage1Test.kt:
--------------------------------------------------------------------------------
1 | package com.example.androidconcepts.rxJava.ownImpl
2 |
3 | import com.example.androidconcepts.common.UiThreadPosterTD
4 | import com.example.androidconcepts.rxJava.ownImpl.ReactObservable.*
5 | import org.junit.Before
6 | import org.junit.Test
7 | import java.util.concurrent.Callable
8 | import java.util.concurrent.CountDownLatch
9 |
10 | class ReactObservableUsage1Test {
11 |
12 | private lateinit var SUT: ReactObservableUsage1
13 | private lateinit var uiThreadPosterTD: UiThreadPosterTD
14 |
15 | @Test
16 | fun subscribeTest() {
17 |
18 | var result = ""
19 | val latch = CountDownLatch(1)
20 |
21 | uiThreadPosterTD.post { // mocked android's ui thread
22 |
23 | SUT.fetchOBS()
24 | .setUiThreadPoster(uiThreadPosterTD)
25 | .subscribe(object : Observer {
26 | override fun onNext(data: String) {
27 | result = data
28 | latch.countDown()
29 | }
30 |
31 | override fun onError(throwable: Throwable) {
32 | latch.countDown()
33 | }
34 | })
35 |
36 | }
37 |
38 | latch.await()
39 | assert(result == "1")
40 | }
41 |
42 | @Test
43 | fun flatMapTest() {
44 |
45 | var result: Int = -1
46 | val latch = CountDownLatch(1)
47 |
48 | uiThreadPosterTD.post { // mocked android's ui thread
49 |
50 | SUT.fetchOBS()
51 | .setUiThreadPoster(uiThreadPosterTD)
52 | .flatMap {
53 | SUT.mapToIntOBS(it)
54 | }
55 | .subscribe(object : Observer {
56 | override fun onNext(data: Int) {
57 | result = data
58 | latch.countDown()
59 | }
60 |
61 | override fun onError(throwable: Throwable) {
62 | latch.countDown()
63 | }
64 | })
65 |
66 | }
67 |
68 | latch.await()
69 | assert(result == 1)
70 | }
71 |
72 | @Test
73 | fun multipleFlatMapsTest() {
74 |
75 | var result = ""
76 | val latch = CountDownLatch(1)
77 |
78 | uiThreadPosterTD.post {
79 |
80 | SUT.fetchOBS()
81 | .setUiThreadPoster(uiThreadPosterTD)
82 | .flatMap {
83 | ReactObservable.fromCallable({
84 | Thread.sleep(100L)
85 | return@fromCallable 2.0
86 | })
87 | }
88 | .flatMap { // fixme : not working
89 | ReactObservable.fromCallable({
90 | Thread.sleep(100L)
91 | return@fromCallable it.toString()
92 | })
93 | }.subscribe(object : Observer {
94 | override fun onNext(data: String) {
95 | result = data
96 | latch.countDown()
97 | }
98 |
99 | override fun onError(throwable: Throwable) {
100 | throw throwable
101 | }
102 | })
103 | }
104 |
105 | latch.await()
106 |
107 | assert(result == "2.0")
108 | }
109 |
110 | @Before
111 | fun setup() {
112 | SUT = ReactObservableUsage1()
113 | uiThreadPosterTD = UiThreadPosterTD()
114 | }
115 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id 'com.android.application' version '7.0.4' apply false
4 | id 'com.android.library' version '7.0.4' apply false
5 | id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
6 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sidsharma2002/AndroidConcepts/e1f45c1e2614fe5b7961f0bbb37989196dcb40de/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Dec 18 11:45:54 IST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "AndroidConcepts"
16 | include ':app'
17 |
--------------------------------------------------------------------------------