diff --git a/app/src/main/java/com/example/androidconcepts/MainActivity.kt b/app/src/main/java/com/example/androidconcepts/MainActivity.kt
index 542ecfb..0c79921 100644
--- a/app/src/main/java/com/example/androidconcepts/MainActivity.kt
+++ b/app/src/main/java/com/example/androidconcepts/MainActivity.kt
@@ -2,13 +2,61 @@ package com.example.androidconcepts
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
+import com.example.androidconcepts.fragments.problems.problem1.FragProbOneFragment
+import com.example.androidconcepts.fragments.problems.problem2.FragProbTwoFragment
class MainActivity : AppCompatActivity() {
+ enum class CURRENT_BUILD {
+ FRAG_PROBLEM_1, FRAG_PROBLEM_2
+ }
+
+ // change this to modify app's state.
+ private val currentBuild = CURRENT_BUILD.FRAG_PROBLEM_2
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// AndroidThreadSpammer().execute()
+
+ if (currentBuild == CURRENT_BUILD.FRAG_PROBLEM_1) {
+ if (savedInstanceState == null) {
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.root_fragment_container, FragProbOneFragment())
+ .commit()
+ }
+ } else if (currentBuild == CURRENT_BUILD.FRAG_PROBLEM_2) {
+ if (savedInstanceState == null) {
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.root_fragment_container, FragProbTwoFragment())
+ .commit()
+ }
+ }
+ }
+
+ override fun onBackPressed() {
+
+// ------------------------------
+// ---------- for FRAG PROBLEM 1
+
+// if (currentBuild == CURRENT_BUILD.FRAG_PROBLEM_1) {
+
+// SOLUTION : manually remove the unintended fragments attached.
+
+// val currentFragment = supportFragmentManager.findFragmentById(R.id.root_fragment_container)
+// ?: return
+
+// Log.d("frag problem", "current frag : " + (currentFragment as FragProbOneFragment).number)
+
+// this does the main fix, i.e remove currently unintended shown fragment from the fm.
+
+// supportFragmentManager.beginTransaction().remove(currentFragment).commit()
+// }
+
+// ---------- for FRAG PROBLEM 1
+// ------------------------------
+
+ super.onBackPressed() // internally does popBackStackImmediate or finish activity.
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/androidconcepts/fragments/problems/problem1/FragProbOneFragment.kt b/app/src/main/java/com/example/androidconcepts/fragments/problems/problem1/FragProbOneFragment.kt
new file mode 100644
index 0000000..d2c4be7
--- /dev/null
+++ b/app/src/main/java/com/example/androidconcepts/fragments/problems/problem1/FragProbOneFragment.kt
@@ -0,0 +1,83 @@
+package com.example.androidconcepts.fragments.problems.problem1
+
+import android.graphics.Color
+import android.os.Bundle
+import android.util.Log
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.FrameLayout.LayoutParams
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import com.example.androidconcepts.R
+import com.google.android.material.button.MaterialButton
+
+class FragProbOneFragment : Fragment() {
+
+ private lateinit var button: MaterialButton
+
+ var number = 0
+
+ private fun getInstance(number: Int): FragProbOneFragment {
+ return FragProbOneFragment().apply {
+ val bundle = Bundle()
+ bundle.putInt("number", number)
+ this.arguments = bundle
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ number = arguments?.getInt("number", 0) ?: 0
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val rootView = FrameLayout(requireContext())
+
+ rootView.layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+
+ rootView.setBackgroundColor(Color.WHITE) // hides the other fragments, due to this I never found this issue.
+ rootView.isClickable = true // prevents clicking through the current fragment.
+
+ button = MaterialButton(requireContext())
+ button.text = "Fragment Problem : $number"
+ val lp = LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT
+ )
+ lp.gravity = Gravity.TOP
+ lp.topMargin = number * 50
+ button.layoutParams = lp
+
+ rootView.addView(button)
+
+ return rootView
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ Log.d("frag problem", "frags size : " + requireActivity().supportFragmentManager.fragments.size
+ + " backstack count : " + requireActivity().supportFragmentManager.backStackEntryCount)
+
+ button.setOnClickListener {
+ val ft = requireActivity().supportFragmentManager.beginTransaction()
+ ft.replace(R.id.root_fragment_container, getInstance(number + 1))
+
+ if (number % 2 == 1) // only add if current fragment number is odd. eg: frag 3 to 4 is added but 4 to 5 isn't.
+ ft.addToBackStack(null)
+
+ ft.commit()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/androidconcepts/fragments/problems/problem2/FragProbTwoFragment.kt b/app/src/main/java/com/example/androidconcepts/fragments/problems/problem2/FragProbTwoFragment.kt
new file mode 100644
index 0000000..288f31b
--- /dev/null
+++ b/app/src/main/java/com/example/androidconcepts/fragments/problems/problem2/FragProbTwoFragment.kt
@@ -0,0 +1,113 @@
+package com.example.androidconcepts.fragments.problems.problem2
+
+import android.graphics.Color
+import android.os.Bundle
+import android.util.Log
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.fragment.app.Fragment
+import com.example.androidconcepts.R
+import com.google.android.material.button.MaterialButton
+
+class FragProbTwoFragment : Fragment() {
+
+ private lateinit var button: MaterialButton
+
+ var number = 0
+
+ private fun getInstance(number: Int): FragProbTwoFragment {
+ return FragProbTwoFragment().apply {
+ val bundle = Bundle()
+ bundle.putInt("number", number)
+ this.arguments = bundle
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ number = arguments?.getInt("number", 0) ?: 0
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val rootView = FrameLayout(requireContext())
+
+ rootView.layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+
+ rootView.setBackgroundColor(Color.WHITE) // hides the other fragments, due to this I never found this issue.
+ rootView.isClickable = true // prevents clicking through the current fragment.
+
+ button = MaterialButton(requireContext())
+ button.text = "Fragment Problem : $number"
+ val lp = FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT
+ )
+ lp.gravity = Gravity.TOP
+ lp.topMargin = number * 50
+ button.layoutParams = lp
+
+ rootView.addView(button)
+
+ return rootView
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ Log.d("frag problem", "$number onSaveInstanceState")
+ super.onSaveInstanceState(outState)
+ }
+
+ override fun onStart() {
+ // state is set to false here. eg : minimize app, state = true, reopen, state = false.
+ Log.d("frag problem", "$number onStart, isStateSaved : $isStateSaved")
+ super.onStart()
+ }
+
+ override fun onResume() {
+ Log.d("frag problem", "$number onResume, isStateSaved : $isStateSaved")
+ super.onResume()
+ }
+
+ override fun onPause() {
+
+ // as far as experimented, isStateSaved is false in onPause.
+ Log.d("frag problem", "$number onPause, isStateSaved : $isStateSaved")
+
+ super.onPause()
+
+ // very common case in which we do a fragment transaction on a network call result.
+ // user initiates the network call and minimises the app and before opening again the result is fetched
+ // and hence a frag transaction is committed.
+ // then perhaps, the app would crash throwing "java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState".
+
+ button.postDelayed({
+ val fm = requireActivity().supportFragmentManager
+ fm.beginTransaction()
+ .replace(R.id.root_fragment_container, this.getInstance(number + 1))
+ .addToBackStack(null)
+ .commit()
+ }, 1500L)
+ }
+
+ override fun onStop() {
+ // weirdly, isStateSaved is true here even when onSaveInstanceState is called yet.
+ // also if I commit frag transactions in onStop then isStateSaved is false.
+ Log.d("frag problem", "$number onStop, isStateSaved : $isStateSaved")
+ super.onStop()
+ }
+
+ override fun onDestroyView() {
+ Log.d("frag problem", "$number onDestroyView, isStateSaved : $isStateSaved")
+ super.onDestroyView()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 712b091..e905b89 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -7,43 +7,9 @@
android:background="@color/white"
tools:context=".MainActivity">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/app/src/test/java/com/example/androidconcepts/common/Benchmark.kt b/app/src/test/java/com/example/androidconcepts/common/Benchmark.kt
index 6a47d08..caf148c 100644
--- a/app/src/test/java/com/example/androidconcepts/common/Benchmark.kt
+++ b/app/src/test/java/com/example/androidconcepts/common/Benchmark.kt
@@ -5,6 +5,6 @@ fun measureTime(action: () -> Unit): Long {
action.invoke()
val endTime = System.currentTimeMillis()
val measuredTime = endTime - startTime
- println("measure time = " + measuredTime + "ms")
+ println("measured time = " + measuredTime + "ms")
return measuredTime
}
\ No newline at end of file
diff --git a/app/src/test/java/com/example/androidconcepts/coroutines/performance/CoroutinePerformanceTest.kt b/app/src/test/java/com/example/androidconcepts/coroutines/performance/CoroutinePerformanceTest.kt
index 06b26bd..2024d28 100644
--- a/app/src/test/java/com/example/androidconcepts/coroutines/performance/CoroutinePerformanceTest.kt
+++ b/app/src/test/java/com/example/androidconcepts/coroutines/performance/CoroutinePerformanceTest.kt
@@ -1,9 +1,13 @@
package com.example.androidconcepts.coroutines.performance
+import com.example.androidconcepts.common.BgThreadPoster
+import com.example.androidconcepts.common.measureTime
import kotlinx.coroutines.*
import org.junit.Test
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
+import java.util.concurrent.Future
+import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
class CoroutinePerformanceTest {
@@ -98,4 +102,65 @@ class CoroutinePerformanceTest {
joinAll(backgroundJob, userJob)
}
+
+ @Test
+ fun performanceTestOfNThreads() {
+ val processImageUseCase = ProcessImageUseCase()
+ val executor = Executors.newFixedThreadPool(8)
+
+ val futures = mutableListOf>()
+
+ measureTime {
+ repeat(1000) {
+ val result = executor.submit {
+ processImageUseCase.processBlocking(10)
+ }
+
+ futures += result
+ }
+
+ futures.forEach {
+ it.get()
+ }
+ }
+
+ // RESULTS :
+ // 1005 Threads, 1000 repeat, 10ms task time -> 1291 ms
+ // 500 Threads, 1000 repeat, 10ms task time -> 1036 ms
+ // 100 Threads, 1000 repeat, 10ms task time -> 690 ms
+ // 50 Threads, 1000 repeat, 10ms task time -> 649 ms
+ // 8 (no of cores) Threads, 1000 repeat, 10ms task time -> 1262 seconds
+ // 5 Threads, 1000 repeat, 10ms task time -> 2 seconds
+ }
+
+ @Test
+ fun performanceTestOfNThreadedDispatcher() {
+ val processImageUseCase = ProcessImageUseCase()
+ val executor = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
+ val scope = CoroutineScope(executor)
+
+ val jobs = mutableListOf()
+
+ measureTime {
+ runBlocking {
+ repeat(1000) {
+ val result = scope.launch {
+ processImageUseCase.processBlocking(10)
+ }
+
+ jobs += result
+ }
+
+ jobs.joinAll()
+ }
+ }
+
+ // RESULTS :
+ // 1005 Threads, 1000 repeat, 10ms task time -> 1344 ms
+ // 500 Threads, 1000 repeat, 10ms task time -> 1056 ms
+ // 100 Threads, 1000 repeat, 10ms task time -> 785 ms
+ // 50 Threads, 1000 repeat, 10ms task time -> 743 ms
+ // 8 (no of cores) Threads, 1000 repeat, 10ms task time -> 1326 seconds
+ // 5 Threads, 1000 repeat, 10ms task time -> 2 seconds
+ }
}
\ No newline at end of file
diff --git a/app/src/test/java/com/example/androidconcepts/coroutines/performance/ProcessImageUseCase.kt b/app/src/test/java/com/example/androidconcepts/coroutines/performance/ProcessImageUseCase.kt
index 0956024..d1df4f1 100644
--- a/app/src/test/java/com/example/androidconcepts/coroutines/performance/ProcessImageUseCase.kt
+++ b/app/src/test/java/com/example/androidconcepts/coroutines/performance/ProcessImageUseCase.kt
@@ -16,4 +16,17 @@ class ProcessImageUseCase {
println("wow, your device has really fast cores")
}
}
+
+ fun processBlocking(time: Int) {
+ // simulate algorithm invocation for time ms in a "blocking manner".
+ // (fake load just to avoid elimination optimization by compilers)
+ val finishTimeMillis = System.currentTimeMillis() + time
+ var counter = 0
+ while (System.currentTimeMillis() < finishTimeMillis) {
+ counter++
+ }
+ if (counter > 250000000) {
+ println("wow, your device has really fast cores")
+ }
+ }
}