i was doing this https://youtu.be/GOpeBbfyb6s?t=1405 with navigation arch but i cant type it.findNavController it is showing red
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lgnbtn.setOnClickListener {
val nameBundle = Bundle()
nameBundle.putString("name",idfield.text.toString())
it.findNavController().navigate(R.id.mainFragment, nameBundle)
}
}
As per the Declaring Navigation dependencies, you must use the -ktx versions of the dependencies to use Kotlin extensions, such as the findNavController() extension for View.
Therefore, replace any dependencies on navigation-fragment with navigation-fragment-ktx and similarly for navigation-ui with navigation-ui-ktx.
Related
I added jetpack compose into my old project and I have noticed one problem with fragmentManager work.
In short, I have the following transactions:
MainActivity -> SettingsFragment -> BackgroundSettingsFragment.
When we go to BackgroundSettingsFragment and we press a back button, we return to SettingsFragment, but its content is empty. I debbuged SettingsFragment, after returning here, everything looks fine, but layout field is just empty.
I added breakpoints to different lifecycle events (onCreate, onCreateView, onViewCreated). Everything works great, I see that resources also is fine.
Without jetpack compose dependencies, there is no problem.
In details, I have the following code.
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion "$rootProject.kotlinVersion"
}
// Dependencies
// --- Jetpack Compose ---
implementation "androidx.compose.ui:ui:$compose_version"
// Integration with activities
implementation "androidx.activity:activity-compose:1.5.1"
// Integration with ViewModels
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
// Tooling support (Previews, etc.)
implementation "androidx.compose.ui:ui-tooling:$compose_version"
// Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
implementation 'androidx.compose.foundation:foundation:1.2.0'
// Material Design
implementation "androidx.compose.material:material:$compose_version"
// Material design icons
implementation "androidx.compose.material:material-icons-core:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
// Integration with observables
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.runtime:runtime-rxjava2:$compose_version"
// Navigation
implementation "androidx.navigation:navigation-compose:2.5.1"
implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
MainActivity:
class MainActivity : RxAppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(layout)
redirectToSettingsFragment()
}
fun redirectToSettingsFragment() {
replaceFragmentSafely(SettingsFragment(), R.id.contentFrame)
}
}
SettingsFragment:
class SettingsFragment : RxFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
setupView()
}
fun setupView() {
label.setText(R.string.background_settings)
value.setText(R.string.color)
}
fun setupToolbar() {
(activity as MainActivity).toolbar.setOnBackButtonClickListener {
activity?.onBackPressed()
}
}
fun redirectToSecondFragment() {
replaceFragmentSafely(BackgroundSettingsFragment(), R.id.contentFrame, allowBackStack = true)
}
}
BackgroundSettingsFragment:
class BackgroundSettingsFragment : RxFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
}
fun setupToolbar() {
(activity as MainActivity).toolbar.setOnBackButtonClickListener {
activity?.onBackPressed()
}
}
}
fun AppCompatActivity.replaceFragmentSafely(fragment: Fragment,
#IdRes containerViewId: Int,
allowBackStack: Boolean = false) {
val fm = supportFragmentManager
.beginTransaction()
.replace(containerViewId, fragment, fragment.javaClass.toString())
if (allowBackStack) fm.addToBackStack(null)
try {
if (!supportFragmentManager.isStateSaved) {
fm.commit()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
Here is the result.
What SettingsFragment looks like before we go to the BackgroundSettingsFragment:
What SettingsFragment looks like after we returned from the BackgroundSettingsFragment:
Please help, maybe someone already faced such a problem.
In my mind, I should add binding.lifecycleOwner=this when I use viewModel.
I find many projects such as Code A doesn't add binding.lifecycleOwner=this, why?
The Code A is from the project https://github.com/enpassio/Databinding
Code A
class AddToyFragment : androidx.fragment.app.Fragment() {
private lateinit var binding: AddToyBinding
...
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(
inflater, R.layout.fragment_add_toy, container, false
)
setHasOptionsMenu(true)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
(requireActivity() as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true)
//If there is no id specified in the arguments, then it should be a new toy
val chosenToy : ToyEntry? = arguments?.getParcelable(CHOSEN_TOY)
//Get the view model instance and pass it to the binding implementation
val factory = AddToyViewModelFactory(provideRepository(requireContext()), chosenToy)
mViewModel = ViewModelProviders.of(this, factory).get(AddToyViewModel::class.java)
binding.viewModel = mViewModel
binding.fab.setOnClickListener {
saveToy()
}
binding.lifecycleOwner=this //I think it should add
}
binding.lifecycleOwner used for observing LiveData with data binding.
Kind of android:text=#{viewModel.text} where val text:LiveData<String>. View will observe text changes at runtime.
as far as I understand it,
binding.lifecycleOwner= this
used in one side to make a subscription to receive messages when liveData is changed (so information in view to be consistent), and in another side to delete observer from list when view and fragment is destroyed (to prevent memory leaks). Fragment's viewLifecycleOwner is more suitable to use as lifecycleOwner for binding in this way, isn't it?
I'm creating a portfolio app that uses Android's new Jetpack Navigation NavController. I've created a fragment with a RecyclerView that will be populated with views that you can click on to navigate to various app demos (and maybe actual apps I've made if I can figure that out). I'm currently populating the list manually, but I'd like to automate that process.
class PortfolioFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_portfolio, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val navController = view.findNavController()
val currentDestination = navController.currentDestination
// How get list of actions?
val appActions = listOf(
R.id.action_portfolioFragment_to_testFragment
)
portfolio_app_list.layoutManager = LinearLayoutManager(context)
portfolio_app_list.adapter = PortfolioAdapter(appActions)
}
}
I couldn't find any methods on NavGraph, nor NavDestination to get the list of actions available for currentDestination. I know that my navigation_main.xml has them, and I could use XMLPullParser or something like that to get them, but I may start using the Safe Args plugin which we'll mean that getting the actual class from the XML would be a pain.
This might be considered a bit hacky.
NavDestination has a private property called mActions which contains the NavActions associated with the NavDestination.
I solved this by using reflection to get access to it.
val currentDestination = findNavController().currentDestination
val field = NavDestination::class.java.getDeclaredField("mActions")
field.isAccessible = true
val fieldValue = field.get(currentDestination) as SparseArrayCompat<*>
val appActions = arrayListOf<Any>()
fieldValue.forEach { key, any ->
appActions.add(any)
}
The NavActions are returned as a SparseArrayCompat which is then iterated to obtain the List
I'm trying to use android-input-mask by RedMadRobot in my Android Kotlin project. But currently, I'm dealing with very strange behavior. The library only works when I disable the Android Navigation Component.
My activity_main.xml layout has the following fragment:
<fragment
android:id="#+id/nav_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/navigation"
app:defaultNavHost="true"/>
Then, in the start destination defined in the navigation component I have:
<EditText
android:id="#+id/test"
android:inputType="number"
android:digits="1234567890+-() "
{ omitted for sake of simplicity } />
Finally, in the the SignUpFragment.kt file I have these lines of code:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val listener = MaskedTextChangedListener.installOn(
test,
"+7 ([000]) [000]-[00]-[00]",
object : MaskedTextChangedListener.ValueListener {
override fun onTextChanged(maskFilled: Boolean, extractedValue: String, formattedValue: String) {
Log.d("TAG", extractedValue)
Log.d("TAG", maskFilled.toString())
}
}
)
test.hint = listener.placeholder()
}
But it does not works, as you can see in the following image:
However, when I hard code the signup fragment in the activity_main.xml file all works fine:
<fragment
android:id="#+id/fragment"
android:name="my.app.SignUpFragment"
{ omitted for sake of simplicity } />
My question is: is there any plausible explanation for this "bug"? Am I making some confusion? How can I solve it?
Thanks for the help.
EDIT:
Same behavior for error messages. If I put this line of code:
test.error = "Error message"
using Android Navigation Component no error message is shown. However, if I hard code the fragment in the main activity layout, the error message is displayed.
Ok, after a lot of time spent in searching for answers, I found that my issue is related to the Android Data Binding Library. More specifically, I need to set listeners and the error message in the binding object created in the onCreateView of my SignUpFragment, as follows:
private lateinit var binding: FragmentSignUpBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_sign_up, container, false)
// saving the instance of FragmentSignUpBinding
binding = DataBindingUtil.setContentView(activity!!, R.layout.fragment_sign_up)
binding.signupViewModel = signUpViewModel
binding.lifecycleOwner = this
setObservers()
return view
}
then, in the onViewCreated:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
MaskedTextChangedListener.installOn(
binding.editCpf, // insted of simply edit_cpf
"[000].[000].[000]-[00]",
object : MaskedTextChangedListener.ValueListener {
override fun onTextChanged(maskFilled: Boolean, extractedValue: String, formattedValue: String) {
Log.d("TAG", extractedValue)
Log.d("TAG", maskFilled.toString())
}
}
)
}
and then it works fine.
I'm using the JetPack Navigation Architecture Components and I can set the title of the Toolbar in my Navigation Graph with android:label="#string/event_lists_toolbar_title". I would also like to set the subtitle, but cannot find a way to do so, except via code in my Fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity as? AppCompatActivity)?.supportActionBar?.subtitle = getString(R.string.my_subtitle)
// ...
}
But the subtitle then stays and I have to reset it after I navigate again to another Screen. I could do this in the onPause() function or like this in my Activity (NavController.OnDestinationChangedListener):
override fun onDestinationChanged(controller: NavController, destination: NavDestination, arguments: Bundle?) {
// resets subtitle of Toolbar
if (destination.id != R.id.fragment_with_subtitle) {
supportActionBar?.subtitle = ""
}
}
Isn't there a better solution for this? Doesn't support Navigation that out of the box?