├── .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 | 6 | 7 | 119 | 120 | 122 | 123 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | ![WhatsApp Image 2023-04-24 at 09 21 22](https://user-images.githubusercontent.com/53833109/233896336-560662a1-2b4c-486b-916f-fe6383ab3e55.jpeg) 35 | 36 | #### Pressed : 37 | ![WhatsApp Image 2023-04-24 at 09 21 22 (1)](https://user-images.githubusercontent.com/53833109/233896369-ddf81603-3e82-436a-8391-94a6b8f190d6.jpeg) 38 | 39 | #### Clicked + Shimmer Animation : 40 | ![WhatsApp Image 2023-04-24 at 09 21 41](https://user-images.githubusercontent.com/53833109/233896450-3285df31-d301-43e4-b798-1cf36322071f.jpeg) 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 | --------------------------------------------------------------------------------