stackoverflow! I'm new to developing in android and I'm trying to figure out Fragments in android. I created a FrameLayout in activity_main.xml
<FrameLayout
android:id="@ id/mainFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
And in onCreate method of MainActivity I'm trying to pass the LoginFragment to the FrameLayout. Here's the code of MainActivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
showFragment(LoginFragment())
passwordInput.addTextChangedListener(
object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) {
logInButton.isEnabled =
passwordInput.text.isNotBlank() && loginInput.text.isNotBlank()
}
})
loginInput.addTextChangedListener(
object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) {
logInButton.isEnabled =
passwordInput.text.isNotBlank() && loginInput.text.isNotBlank()
}
})
logInButton.setOnClickListener {
showFragment(LoginFragment())
}
}
private fun showFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction()
.replace(R.id.mainFragment, fragment)
.commit()
}}
The LoginFragment code:
class LoginFragment : Fragment(R.layout.fragment_login) {}
And the fragment_login.xml file:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@ id/loginInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:layout_marginTop="80dp"
android:layout_marginEnd="40dp"
android:autofillHints="username"
android:hint="@string/enter_login"
android:inputType="textEmailAddress" />
<EditText
android:id="@ id/passwordInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="40dp"
android:layout_marginBottom="20dp"
android:autofillHints="password"
android:hint="@string/enter_password"
android:inputType="textPassword" />
<Button
android:id="@ id/logInButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:layout_marginEnd="40dp"
android:enabled="false"
android:text="@string/log_in" />
</LinearLayout>
I got the null pointer:
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.EditText.addTextChangedListener(android.text.TextWatcher)' on a null object reference at com.example.fragmentactivity.MainActivity.onCreate(MainActivity.kt:18)
I can't really understand what's wrong. It says that I'm trying to refer an empty veiw but all thoose views are viewBinded and in IDE they are shown as they should be. Thanks in advance for all the hints and answers
CodePudding user response:
for me it doesn't make sense to import a fragment to your main activity, if you need a layout to use it anywhere , just create a layout otherwise implement login requirements inside login fragment
CodePudding user response:
What do you mean when you say "the views are viewBinded" - are you using View binding? Or are you using the (deprecated) Kotlin synthetic extensions? Because it looks like the latter
If you are using View binding (and you've not shown yourself accessing the binding object for some reason) you'll have to create a new binding object for the Fragment
's layout, since you're adding it at runtime and it has nothing to do with MainActivity
's layout XML.
If you're using the synthetic extensions, the recommendation is don't - they're deprecated for a reason, and it's better to move over to View binding. I'm not sure how that interacts with your added layout - from what I know it should work ok, but I also know it won't complain if you reference a View
ID in a completely different layout, because it doesn't know what you're actually doing at compile time. It can't make any safety guarantees, so the fact the IDE isn't complaining doesn't tell you anything except "those IDs exist in some layout file" (another reason it's recommended to move away from this extension).
If you're unsure about whether View
lookups are working, do it the old-school way with findViewById
. If the view has been added to the Activity
's layout hierarchy, that will find it.
As for why it's happening - well there's a couple of things wrong here.
First you're calling commit()
instead of commitNow()
in your FragmentTransaction
:
public abstract int commit()
Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.
public abstract void commitNow()
Commits this transaction synchronously. Any added fragments will be initialized and brought completely to the lifecycle state of their host and any removed fragments will be torn down accordingly before this call returns. Committing a transaction in this way allows fragments to be added as dedicated, encapsulated components that monitor the lifecycle state of their host while providing firmer ordering guarantees around when those fragments are fully initialized and ready. Fragments that manage views will have those views created and attached.
Basically, if you just call commit()
, the Fragment
will be added to your Activity
later. You're immediately trying to access views on it, but it's probably not added to the view hierarchy yet.
The second issue is that to access views on a Fragment
, it has to have at least got to the onCreateView
stage of its lifecycle, otherwise ain't no views to access! commitNow()
up there seems to guarantee that it will ensure the Fragment
will at least be at that stage when it returns, so the views should be there to access.
The problem here really is that your Fragment
should be handling all this stuff itself, not the Activity
. If you have a Fragment with some text fields that need validation, that should be something the Fragment takes care of, because it's all happening inside the Fragment, as a piece of UI that does a particular task. It can communicate with its parent activity if it needs to, but the Activity
should ideally be doing as little as possible.
So doing all this stuff through the Fragment
itself, encapsulating it, would avoid a lot of these problems. In onCreateView
or onViewCreated
you'd just set up the TextWatcher
s on the View
s (which have definitely been created at this point) and now your Fragment is ready to go. Your Activity
doesn't need to know what's going on in there, doesn't need to know what View
s are in there or any of that. Separation of concerns makes things easier to work with, y'know? It helps!