Coroutines getting and setting a theme on an activity - kotlin

How would I set the theme of an activity before it is created, when the theming information must be retrieved from DataStore?
Since the data needs to come immediately, a runBlocking has been necessary. Can I work around using a runBlocking in this case?
Example:
override fun onCreate(savedInstanceState: Bundle?) {
runBlocking {
val theme = settingsRepository.getTheme()
this#ThemedActivity.setTheme(theme)
}
super.onCreate(savedInstanceState)
}

Related

avoid Error Suspension functions can be called only within coroutine body Kotlin [duplicate]

I am calling suspended function from onCreate(...)
override fun onCreate(savedInstanceState: Bundle?) {
...
...
callGetApi()
}
and the suspended function is:-
suspend fun callGetApi() {....}
But the error shows up Suspend function 'callGetApi' should be called only from a coroutine or another suspend function
Suspend function should be called only from a coroutine.
That means to call a suspend function you need to use a coroutine builder, e.g. launch, async or runBlocking(recommended to use only in unit tests). For example:
class Activity : AppCompatActivity(), CoroutineScope {
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launch {
val result = callGetApi()
onResult(result) // onResult is called on the main thread
}
}
suspend fun callGetApi(): String {...}
fun onResult(result: String) {...}
}
To use Dispatchers.Main in Android add dependency to the app's build.gradle file:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
The MOST RECENT APPROACH would be to use extension properties in ViewModel and Activity/Fragment:
In ViewModel we can use viewModelScope to launch a coroutine:
viewModelScope.launch { ... }
It attached to the lifecycle of Activity/Fragment and cancels launched coroutines when they destroyed.
Similar in Activity/Fragment we can use the following extension properties to launch a coroutine:
lifecycleScope.launch {}, lifecycle.coroutineScope.launch {}, viewLifecycleOwner.lifecycleScope.launch {}(applicable in Fragments).
Looks like the most elegant way to do it as of July 2019, is the one described here:
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super...
lifecycleScope.launch {
val result = callGetApi()
onResult(result)
}
}
}
Don't forget to add the correponding lib:
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha02"
The above answer worked , but i solved it without inheriting CoroutineScope class by just using ....
gradle.build
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
}
Activity.kt
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Dispatchers
GlobalScope.launch (Dispatchers.Main) { callGetApi() }
Dispatchers.Main is important cause you cannot update the UI in any other thread than main.
But its recommended to inherit CoroutineScope to maintain the lifecycle of the activity and onDestroy of the activity to kill the job

Activities vs Fragments for apps

I have been trying to figure this out for a bit now. I see a lot of developers and youtubers (tutorials) saying that we should use as little activities as possible in order for a faster, more efficient and less resource heavy code/app. I was wondering if there is a way to create a Log-in and Sign-up using only the MainActivity for both Log-in and Sign-Up in combination with fragments for navigation between them.
Or
Do we need atleast 2 or more activities to handle that process ( Log-in & Sign-Up )?
Example: 1 activity for log-in and 1 activity for sign-up.
Appreciate and welcome any answers regarding this topic!
Theoretically, you could have your entire application run on a single Activity and use Fragments for all of the pages.
Each fragment has its own lifecycle within the activity.
MainActivity can look like this
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadFragment(2)
}
public fun loadFragment(page: Int){
if(page == 1){
// load login
val manager = supportFragmentManager.beginTransaction()
manager.replace(R.id.fragment_holder, LoginFragment()).commit()
}else{
// load register
val manager = supportFragmentManager.beginTransaction()
manager.replace(R.id.fragment_holder, LoginFragment()).commit()
}
}
}
LoginFragment can look like this
class LoginFragment : Fragment() {
lateinit var myButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_login, container, false)
myButton = view.findViewById(R.id.my_button)
myButton.apply {
setOnClickListener { toRegister() }
}
return view;
}
fun toRegister(){
// replace the fragment in main activity with register fragment
(requireActivity() as MainActivity).loadFragment(1)
}
}
RegisterFragment can look like this
class RegisterFragment : Fragment() {
lateinit var mButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_register, container, false)
mButton = view.findViewById(R.id.my_button)
mButton.apply {
setOnClickListener { toLogin() }
}
return view;
}
fun toLogin(){
// replacing the fragment in the main activity with the login fragment
(requireActivity() as MainActivity).loadFragment(1)
}
}
Basically, we replace the fragment displayed in the activity by calling loadfragment.
This logic can be applied to as many fragments as is necessary.

Why is setContent{} being called twice?

I've recently started my first project using Jetpack Compose (with minimal Android dev experience).
For checking performance, I logged each call to any function / composable, with some unexpected behavior at startup or orientation change (but without further interacting with the app):
I do understand oncreate / super being called (again, in case of orientation change), but why is it that setContent {} is being called twice?
override fun onCreate(savedInstanceState: Bundle?) {
Log.v(tag, "oncreate")
super.onCreate(savedInstanceState)
Log.v(tag, "oncreatesuper")
setContent {
Log.v(tag, "setting content")
Content()
}
}
and then
#Composable
private fun Content() {
val arrayOfNodes = rememberSaveable { mutableListOf<Wurzel>() }
val toggleSizeInputDialog = rememberSaveable { mutableStateOf(false) }
(...some more...)
val currentConfig = LocalConfiguration.current
val title = stringResource(id = R.string.title)
Log.v(tag, "recomposing content")
MyTheme {
when (currentConfig.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
Scaffold(...........)
My project is far to small to see any perfomance issues, however I'd like to find out the reason for this behavior for future reference or whether I severely misunderstood the compose architecture.
setComponent() is not being called twice, but the composable function you pass in it is. It just seems like some event causes recomposition of the content composable.

onMapReady function seems to not be called on android app

The map seems to be created but the marker does not get placed. Most questions relating to this problem seems to be missing getmapasync but th,s one seems to have it
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Retrieve the content view that renders the map.
setContentView(R.layout.fragment_dashboard)
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as? SupportMapFragment
mapFragment?.getMapAsync(this)
}
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
val sydney = LatLng(-33.852, 151.211)
mMap.addMarker(
MarkerOptions()
.position(sydney)
.title("Marker in Sydney")
)
// [START_EXCLUDE silent]
mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
// [END_EXCLUDE]
}
// [END maps_marker_on_map_ready_add_marker]
Is your map fragment nested in a fragment?
If you are calling getMapAsync from a fragment that contains the actual map, you need to use childFragmentManager instead of supportFragmentManager to find the map
(childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment).getMapAsync(this)

how to remove the pager fragment from activity to prevent the old fragment instance to be restored by os

It is an activity hosting a pager fragment which has android.support.v4.view.ViewPager and the adapter class derived from FragmentStatePagerAdapter.
The problem is from the pager fragment it has two or three fragments cached, but the data is not parseable (including a view dynamically getting from a 3rd part sdk), when sometime os recreate this fragment in case like minimize/reopen it, or after rotation the restored fragment are blank (using the one from cache and lacking data).
Not find a way to re-populate the fragments restored by os through the pager fragment's recreation flow.
Even tried in the pager fragment to clear the adapter's data in onActivityCreated()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val childFragmentManager = childFragmentManager
mAdapter = ContentPagerAdapter( activity as Context,
childFragmentManager, emptyList())
mContentViewPager!!.setAdapter(mAdapter)
mAdapter?.setData(emptyList())
}
the adapter:
fun setData(itemsList: List<Data>) {
this.data = itemsList
notifyDataSetChanged()
}
the viewPager seems still showing the previous cachhed fragment without expected data after the re-creation flow is complete. (tried to make sure it is from the cached one, by in the fragment's onsaveInstance() to save the position of the data in the adapter data list, and the re-created fragment in the viewPager got that position, so it is a os re-created one from cache. But how could it be after at beging already set the adapter with empty list at onActivityCreated()).
Cannt resolve with clean the fragments by setting data to empty.
So tried to remove the page fragment from the activity at onDestroy() with hope that there will be no fragment to be put in cache:
class ContentPagerFragment :Fragment {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.content_pager, container, false)
mContentViewPager = view.findViewById(R.id.contentViewPager)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val childFragmentManager = childFragmentManager
mAdapter = ContentPagerAdapter( activity as Context,
childFragmentManager, emptyList())
mContentViewPager!!.setAdapter(mAdapter)
mAdapter?.setData(emptyList())
}
interface RemoveFragmentAtClose {
fun onContentPagerFragmentDoestroy()
}
override fun onDestroy() {
(activity as? RemoveAtCloseFragment)?
.onContentPagerFragmentDoestroy()
super.onDestroy()
}
}
and in the hosting activity
class HostActivity : AppCompatActivity(),
ContentPagerFragment.RemoveFragmentAtClose {
override fun onContentPagerFragmentDoestroy() {
supportFragmentManager.beginTransaction().
remove(supportFragmentManager.findFragmentById(R.id.pagerFragmentContainer))
.commitAllowingStateLoss()
var contentPagerFragment = ContentPagerFragment::class.java.cast(supportFragmentManager
.findFragmentById(R.id.pagerFragmentContainer))
Log.i(TAG, " onContentPagerFragmentDoestroy(), this $this" +
"\ngetContentPagerFragment: $contentPagerFragment"
)
}
override fun onAttachFragment(fragment: Fragment) {
Log.d(TAG, " onAttachFragment(), " +
"fragment: $fragment \nthis $this")
}
}
But the log shows that the supportFragmentManager.findFragmentById(R.id.pagerFragmentContainer) still find the fragment after the .commitAllowingStateLoss() call.
And when os restores the activity the ContentPagerFragment is showing up in the activity's onAttachFragment(fragment: Fragment) before activity's onCreate(savedInstanceState: Bundle?) is called.
Why is the viewPager using the old fragment after remove the pager fragment from the activity's supportFragmentManager?
How to prevent the view pager from use the cached fragment?