java.lang.RuntimeException: Unable to start activity ComponentInfo{}: kotlin.KotlinNullPointerException - android

I've created a DialogBox myDialog and initialised it in Settings.kt activity and I am getting the above error when I am initialising the themeRadioGroup, although I've used a non-null assertion operator(!!).
What I want to do is to change the theme of the app using radio buttons(light or dark). And those two radio buttons are in a dialog box i.e. myDialog and Settings.kt is the activity of which I want to change theme, so that's why I want to access the radio group from myDialog.
Settings.kt Activity
class Settings : AppCompatActivity() {
private lateinit var themeRadioGroup : RadioGroup
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
val myDialog = Dialog(this)
myDialog.setContentView(R.layout.app_info_popup)
themeRadioGroup = myDialog.findViewById(R.id.theme_radio_group)!!
//...
}

The !! is the only way you can get a KotlinNullPointerException. The exception means your assertion that the item is non-null is wrong. In this case, it means a view with that ID does not exist in the dialog layout you have set.
Maybe the view you are looking for is in the activity layout, in which case you should call this.findViewById instead of myDialog.findViewById.
Otherwise, check your dialog layout closely to make sure it is there. If you have multiple versions of the layout for different configurations, make sure they all have a view with that ID.
You might want to look into view binding, which is designed to avoid this kind of problem.

Related

Presenter is not called in onClick

When trying to inflate and set my presenter to my databinding component in this way my presenter methods are not called.
val fragmentBinding = FragmentListEditBinding.inflate(layoutInflater)
fragmentBinding.presenter = ListEditorPresenter(this, requireContext())
but when using this
val fragmentBinding = DataBindingUtil.setContentView<FragmentListEditBinding(requireActivity(), R.layout.fragment_list_edit)
fragmentBinding.presenter = ListEditorPresenter(this, requireContext())
It works fine, but then the layout is covering the full screen.
Any ideas how to fix this issue?
Please tell me if more context is needed.
The second method is for activity, not for the fragment, For fragment, you have to do it in the first method.
Before DataBinding and ViewBinding, In and activity we call setContentView(R.layout.activity_main) to set the view for the activity, but for fragment, we override the onCreateView method and inflate a view and return it.
So the way of setting view for activity and fragment is different from the beginning.
So the DataBindingUtil.setContentView is made for activity, while the FragmentListEditBinding.inflate custom/manual inflation is made for fragment. As i have already mentioned it above.

How can I use button in one activity to do something in second activity?

I got a question. I have two activities in my app. In first one, when I click the button, I need something to happen in the second one. How can I do it? If that button would be in the second activity I would just do it by:
button.setOnClickListener {}
But how can I do it when button is in the other activity? It's worth adding that code, that tells what should happen, must be in that second activity, just like it was in that "setOnClickListener". Sorry, I'm starting with Android development.
You could communicate between two activities via broadcast or intent.
But it make logic more complex.
So I suggest use two fragments instead two activities.
If you use two fragment in one activity, you can easy communicate between two fragments.
You can look more detailed information about fragment from this URL.
https://developer.android.com/guide/fragments
To achieve the intended flow you may try the below approach,
Start activity 2 on button click from activity 1.
On activity 2 place your code in onCreate so, once activity 2 loads up your code will fire up.
Activity 1:
button.setOnClickListener{
val intent = Intent(this, second::class.java)
startActivity(intent)
}
Activity 2:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CallDefinedFunctionHere();
}
You cannot be sure that both activities are present at the same moment since the system might destroy inactive one therefore you cannot trigger any code from activity A inside activity B.
What you can do you can start activity B with an intent and some parameters describing what should happen inside activity B.
or
You can communicate by writing something down to a persistent storage (like SharedPreferences) and then when the other activity is resumed (active again) reading it, reacting to it and then removing it from the storage (to make sure you do not handle it twice).
You can pass data in the intent that opens the second activity.
// In first activity:
buttonX.setOnClickListner {
val intent = Intent(MySecondActivity::class.java).apply {
putExtra("wasFromButtonX", true)
}
startActivity(intent)
}
// In second activity:
fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val wasLaunchedFromButtonX = intent.getBooleanExtra("wasFromButtonX", false)
// Above line uses false as default, so it will only be true if you explicitly
// put the extra in the Intent that started this Activity.
if (wasLaunchedFromButtonX) {
// do alternate setup here
}
}
How to pass information between Activities is explained in the introductory documentation here.
Create a function in class where 2nd activity are defined like this.
public void refresh(){}
Now Call that in your 1st activity where you want to call 1st after any action.
button.setOnClickListener {((MainActivity) Objects.requireNonNull(getActivity())).refresh();}

Can a Dialog have a view model in android?

I need to do an API call from a Dialog. Do I need to go back to the fragment for doing so, or is there any way to refer to the fragment view model?
Yes, it is possible and I was able to do that because class DialogFragment extends Fragment. So I added a view model just like any other fragment.
like below where BaseDialog class extends DialogFragment
You can try with this:
Use interface, implement it in fragment so you have callback funtion.
Pass high ordered function, declare it like this in dialog:
var click: (() -> Unit)? = null;
Then you can set it from fragment when you instantiate your dialog.
Use shared view model, for example make view model in your activity and then you can access it from every fragment or dialog like this:
(requireActivity() as MainActivity).viewModel
Like this you can set value in view model variable (liveData often) inside your dialog and observe changes in fragment
I think you can pass a high ordered function to dialog and handle it in fragment using viewModel inside.

How do I properly access my MainActivity from outside its class?

I have a function of which I call from MainActivity
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
doSomethingFunction(this)
}
...
doSomethingFunction(activity: AppCompatActivity)
{
var button = activity.findViewById<Button>(R.id.button)
// if I try do something with button the app crashes eg
button.text = "Text"
}
Which leads me to believe somehow I must not be accessing activity from MainActivity and activity.findViewById<Button>(R.id.button) didn't actually return my buttons id thats why its crashing. I tried to extend doSomethingFunction(activity: MainActivity) but it crashes the same.
How do I properly pass "MainActivity" to doSomethingFunction so that I can do stuff with "activity"
you don't have a setContentView so yes, it will crash, because there's no layout associated with this activity.
you have to use setContentView(R.layout.yourLayoutHere) in order for you to be able to access xml components.
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.something)
doSomethingFunction(this)
}
a_local_nobody answered my question correctly. I am just posting so that others may benefit. The are two reasons I was facing that error
Reason 1
Just as my code denotes I was calling doSomethingFunction(this) before setContentView thats why there was no setContentView in my sample code. So I couldn't access members in MainActivity well
This lead me to believe in my own project I wasn't initialising my button in time. So After investigations the culprit is in the xml of which a_local_nobody encouraged me to post. I couldn't do that because it was a complex project and I didn't know which part of the xml.
Reason 2
I was using drawer layout which was in turn loading a layout with my button
<androidx.drawerlayout.widget.DrawerLayout
app:headerLayout="#layout/layout_with_button">
This was causing a delay and at run time the button would be null so the app was crashing.
I put the button as a child of Drawer layout and the app runs fine. I deleted app:headerLayout="#layout/layout_with_button"
<androidx.drawerlayout.widget.DrawerLayout>
<Button/>
</androidx.drawerlayout.widget.DrawerLayout>

How can I code buttons to change layouts in Kotlin?

I am trying to make a text-based choice game for Android, very similar to the game called "Magium"
https://play.google.com/store/apps/details?id=com.magiumgames.magium&hl=en
Users will choose from a few options by pressing respective buttons to progress the story. After a buttons press, I want the app to show another layout. Let's say there is only one button (named button1) in page1. Pressing it changes the layout to page2. I achieved this by:
button1.setOnClickListener {
setContentView(R.layout.page2)
}
Again, for simplicity, let's say there is only one button (named button2) in page2. I want to show page3 after user presses button2.
I added the same code below the previous code as:
button1.setOnClickListener {
setContentView(R.layout.page2)
}
button2.setOnClickListener {
setContentView(R.layout.page3)
}
However, the app doesn't open when I do this. I am just beginning to learn programming. Could someone point me in the right direction?
You should not, in most cases, call setContentView() multiple times. You can read more in this answer.
Is there a point to change the whole layout to only update the content of it? You should be updating the content of your UI when the user clicks, not inflating another layout.
Did you check if the clickListener for the second button is called?
Maybe if you set the click listener before calling setContentView(R.layout.page2) is will not have the button displayed for you to register to it.
But i agree with alvarezfmb, you should use a better solution for changing the views, maybe Fragments or even hiding and showing views is better than this solution.
You could use fragment.
First, use in your activity layout as a container of Fragment, here we give it a id like, android:id="+id/fragment_container"
Second, create a layout xml file for your fragment, here we give it a name, like fragment_page_3.xml
Then create a subclass of Fragment, and override its onCreateView() method, where you inflate your fragment's layout. Code here
class Page3Fragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_page_3, container, false)
}
}
Finally, in clickListener, you could use fragmentManager load your fragment, code like this:
button2.setOnClickListener {
val fragment = Page3Fragment()
val fm = supportFragmentManager
fm.beginTransaction()
.add(R.id.fragment_container,fragment)
.commit()
}
I just want to give you some ideas.
You cannot write code like this, this is wrong implementation for your app because you may need several fragment and you need a Callbacks interface implemented by your activity.
If you could change your content of UI, it's a better way.
To learn more, you could refer https://developer.android.com/guide/components/fragments

Categories

Resources