what does the "root" field in a view binding class represent? - findviewbyid

class MainActivity : AppCompatActivity() {
private lateinit var myBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(myBinding.root)
}
}
myBinding.root refers to the parent view in my xml file which is a ConstraintLayout. This is my xml code
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/rootConstraint"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
Now, consider this
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val rootConstraint = findViewById<View>(R.id.rootConstraint)
setContentView(rootConstraint)
}
}
when i create a direct reference to the ConstraintLayout using findViewByID and pass it to setContentView, the app crashes. why's that?
Do myBinding.root and rootConstraint not refer to the same view?

Related

How to initialize Swift class annotated #MainActor for XCTest, SwiftUI Previews, etc

We'd like to make use of the #MainActor Annotation for our ViewModels in an existing SwiftUI project, so we can get rid of DispatchQueue.main.async and .receive(on: RunLoop.main).
#MainActor
class MyViewModel: ObservableObject {
private var counter: Int
init(counter: Int) {
self.counter = counter
}
}
This works fine when initializing the annotated class from a SwiftUI View. However, when using a SwiftUI Previews or XCTest we also need to initialize the class from outside of the #MainActor context:
class MyViewModelTests: XCTestCase {
private var myViewModel: MyViewModel!
override func setUp() {
myViewModel = MyViewModel(counter: 0)
}
Which obviously doesn't compile:
Main actor-isolated property 'init(counter:Int)' can not be mutated from a non-isolated context
Now, obviously we could also annotate MyViewModelTests with #MainActor as suggested here.
But we don't want all our UnitTests to run on the main thread. So what is the recommended practice in this situation?
Annotating the init function with nonisolated as also suggested in the conversation above only works, if we don't want to set the value of variables inside the initializer.
Just mark setUp() as #MainActor
class MyViewModelTests: XCTestCase {
private var myViewModel: MyViewModel!
#MainActor override func setUp() {
myViewModel = MyViewModel(counter: 0)
}
}
Approach:
You can use override func setUp() async throws instead
Model:
#MainActor
class MyViewModel: ObservableObject {
var counter: Int
init(counter: Int) {
self.counter = counter
}
func set(counter: Int) {
self.counter = counter
}
}
Testcase:
import XCTest
#testable import Demo
final class MyViewModelTests: XCTestCase {
private var myViewModel: MyViewModel!
override func setUp() async throws {
myViewModel = await MyViewModel(counter: 10)
}
override func tearDown() async throws {
myViewModel = nil
}
func testExample() async throws {
await myViewModel.set(counter: 20)
}
}

How to set an alarm with jetpack compose?

I am triyng to set an alarm with Jetpack Compose but does not work i am triyng to test with emulator and with app running on main thread, this is my code:
class MainActivity : ComponentActivity() {
lateinit var navController: NavHostController
private var alarmMgr: AlarmManager? = null
private lateinit var alarmIntent: PendingIntent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val context= LocalContext.current
navController = rememberNavController()
NotePadReminderTheme {
val calendar: Calendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
set(Calendar.HOUR_OF_DAY,6)
set(Calendar.MINUTE,34)
}
val alarmManager =
context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
val intent = Intent(context, AlarmReceiver::class.java)
val pendingIntent =
PendingIntent.getService(context, 3, intent,0)
alarmManager?.setExact(AlarmManager.ELAPSED_REALTIME,calendar.timeInMillis,pendingIntent)
// MainPage(navController =navController )
}
}
}
}
And this is my Manifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<application
android:allowBackup="true"
android:dataExtractionRules="#xml/data_extraction_rules"
android:fullBackupContent="#xml/backup_rules"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.NotePadReminder"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="#string/app_name"
android:theme="#style/Theme.NotePadReminder">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<receiver android:name="com.mobile.notepadreminder.AlarmReceiver" />
</application>
</manifest>
And this is my AlarmReceiver class:
class AlarmReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d("alarm","received")
}
}
I do not know why does not work.
My problem is that I set the alarm and the alarm does not run never.
The only thing I needed was to change the getService method to getBroadcast but I don't know why
class MainActivity : ComponentActivity() {
lateinit var navController: NavHostController
private var alarmMgr: AlarmManager? = null
private lateinit var alarmIntent: PendingIntent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val context= LocalContext.current
navController = rememberNavController()
NotePadReminderTheme {
val calendar: Calendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
set(Calendar.HOUR_OF_DAY,6)
set(Calendar.MINUTE,34)
}
val alarmManager =
context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
val intent = Intent(context, AlarmReceiver::class.java)
val pendingIntent =
PendingIntent.getBroadcast(context, Date().seconds, intent, PendingIntent.FLAG_IMMUTABLE)
alarmManager?.setExact(AlarmManager.ELAPSED_REALTIME,calendar.timeInMillis,pendingIntent)
// MainPage(navController =navController )
}
}
}
}

hiltViewModel() function gives a new instance of the same composable function every time navigating to it

I've got a simple navhost that looks like this:
#Composable
fun NavigationComponent(
navController: NavHostController,
context: Context,
) {
NavHost(
navController = navController,
startDestination = Screen.MainScreen.route
) {
composable(Screen.MainScreen.route) {
MainScreen(navController = navController)
}
composable(
route = Screen.NewPlanScreen.route,
) {
NewPlanScreen(context = context, navController = navController)
}
}
}
It is called from main activity like this:
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
#OptIn(ExperimentalAnimationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val context = this
val navController: NavHostController = rememberNavController()
MainTheme(
darkTheme = isDarkMode.value
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(AppTheme.colors.primaryBackground)
) {
NavigationComponent(navController = navController, context = context)
}
}
}
}
}
NewPlanScreen has a viewmodel that is provided by dagger hilt:
#Composable
fun NewPlanScreen(
viewModel: NewPlanScreenViewModel = hiltViewModel(),
navController: NavController,
context: Context
) {
//...
}
Also, the NewPlanScreenViewModel has a #HiltViewModel annotation and an injected constructor.
And in the MainScreen I use
navController.navigate(Screen.NewPlanScreen.route)
but when I press a back button on a phone to navigate back to main screen and then call the
navController.navigate(Screen.NewPlanScreen.route)
again, a new ViewModel for a NewPlan is created. What am I doing wrong?

kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen <object> when using ktor in Kotlin Multiplatform (iOS)

I am trying to build a simple Kotlin Multiplatform app that calls to the internet to fetch some Strings from the internet with ktor. I took some functions from Kotlin conference app which I compiled and it works fine on both Android and iOS.
However, in my sample app, it only works on Android, but on iOS it returns
kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen <object>#c422ffe8
Here is the GitHub repository and below is my code:
// src/commonMain/CoroutinePresenter.kt
open class CoroutinePresenter(
private val mainContext: CoroutineContext, // TODO: Use Dispatchers.Main instead when it will be supported on iOS
private val baseView: BaseView
): CoroutineScope {
private val job = Job()
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
baseView.showError(throwable)
}
override val coroutineContext: CoroutineContext
get() = mainContext + job + exceptionHandler
open fun onDestroy() {
job.cancel()
}
}
--
// src/commonMain/SamplePresenter.kt
class SamplePresenter(
val uiContext: CoroutineContext,
baseView: BaseView,
val sampleView: SampleView
) : CoroutinePresenter(uiContext, baseView) {
private val client = HttpClient()
fun callSimpleApi() {
try {
GlobalScope.launch(uiContext) {
getToolString()
}
} catch (e: Exception) {
sampleView.returnString(e.toString())
}
}
suspend fun getToolString() = client.get<String> {
url("https://tools.ietf.org/rfc/rfc1866.txt")
}
override fun onDestroy() {
super.onDestroy()
}
}
--
// src/iosMain/SampleIos.kt
object MainLoopDispatcher: CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
NSRunLoop.mainRunLoop().performBlock {
block.run()
}
}
}
--
// iosApp/iosApp/ViewController.swift
import app
class ViewController: UIViewController, SampleView, BaseView {
private lazy var presenter: SamplePresenter = { SamplePresenter(
uiContext: MainLoopDispatcher(),
baseView: self,
sampleView: self
)
}()
#IBOutlet weak var label: UILabel!
func showError(error: KotlinThrowable) {
print(error.message)
}
func returnString(result: String) {
label.text = result
print(result)
}
override func viewDidLoad() {
super.viewDidLoad()
print("helo")
presenter.callSimpleApi()
}
}
Turns out that the Kotlin version 1.3.11 is causing the trouble. I have downgraded it to 1.3.10 and it works perfectly fine. ktor will receive a fix in the next minor release.
Source - Kotlin Slack, multiplatform channel.

Dagger 2, can not inject presenter in my activity

I am new in Dagger2.
I have a problem with inject presenter in my activity
I try to resolve my problem after read this article https://android.jlelse.eu/inject-interfaces-without-providing-in-dagger-2-618cce9b1e29 but it was not helpe me. I hope someone help me, I spend all day tring to resolve it...
here is my modules:
#Module
class AppModule(private val appContext: Context) {
#Singleton
#Provides
internal fun provideContext(): Context {
return appContext
}
#Singleton
#Provides
internal fun providePreferences(): SharedPreferences {
return appContext.getSharedPreferences(
appContext.resources.getString(R.string.shared_preferences_name), Context.MODE_PRIVATE)
}
}
#Module
abstract class ActivityModule {
#Binds
abstract fun provideMakeCheckPresenter (p :
MakeCheckPresenter<MakeCheckMvpView>)
: MakeCheckMvpPresenter<MakeCheckMvpView>
}
here is my component:
#Component(modules = { AppModule.class, ActivityModule.class})
#Singleton
public interface AppComponent {
void inject(MakeCheckActivity makeCheckActivity);
}
here is my App class:
class App : android.support.multidex.MultiDexApplication() {
override fun onCreate() {
super.onCreate()
component = buildComponent()
}
protected fun buildComponent(): AppComponent {
return DaggerAppComponent.builder().appModule(AppModule(this)).build()
}
companion object {
var component: AppComponent? = null
private set
}
}
here is my activity:
class MakeCheckActivity : BaseActivity(), MakeCheckMvpView {
#Inject lateinit var presenter: MakeCheckMvpPresenter<MakeCheckMvpView>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.new_check_activity)
App.getComponent().inject(this)
}
}
here is my presenter:
class MakeCheckPresenter<V : MakeCheckMvpView>
#Inject constructor() : BasePresenter<MakeCheckMvpView>(), MakeCheckMvpPresenter<MakeCheckMvpView> {
override fun saveEnterpriseId(enterpriseId: Int) {
//model.enterpriseId
}
}
here is interfaces for presenter and view:
interface MakeCheckMvpView : MvpView {
}
interface MakeCheckMvpPresenter<in V : MakeCheckMvpView> : MvpPresenter<V> {
fun saveEnterpriseId(enterpriseId : Int)
}
and here base classes:
open class BasePresenter<V : MvpView> #Inject constructor(): MvpPresenter<V> {
}
abstract class BaseActivity : AppCompatActivity(), MvpView{
}
and always when i build i have this error:
...\di\component\AppComponent.java:62: error: ..ui.check_making.MakeCheckMvpPresenter<? super ...ui.check_making.MakeCheckMvpView> cannot be provided without an #Provides- or #Produces-annotated method.
e:
e: void inject(MakeCheckActivity makeCheckActivity);
how can I resolve it?

Resources