Navigation with View Binding - android-navigation

I'm trying to replace all findViewById using View Binding. But, I can't change my NavController code line using View Binding.
val navController = findNavController(this, R.id.mainHostFragment)
to
var binding : ActivityMainBinding
val navController = findNavController(this, binding.mainHostFragment)
How can I do that?

You can't replace it with View Binding.
findNavController does more than finding the view in the layout.
Have a look at the source code here
/**
* Find a {#link NavController} given a local {#link Fragment}.
*
* <p>This method will locate the {#link NavController} associated with this Fragment,
* looking first for a {#link NavHostFragment} along the given Fragment's parent chain.
* If a {#link NavController} is not found, this method will look for one along this
* Fragment's {#link Fragment#getView() view hierarchy} as specified by
* {#link Navigation#findNavController(View)}.</p>
*
* #param fragment the locally scoped Fragment for navigation
* #return the locally scoped {#link NavController} for navigating from this {#link Fragment}
* #throws IllegalStateException if the given Fragment does not correspond with a
* {#link NavHost} or is not within a NavHost.
*/
#NonNull
public static NavController findNavController(#NonNull Fragment fragment) {
Fragment findFragment = fragment;
while (findFragment != null) {
if (findFragment instanceof NavHostFragment) {
return ((NavHostFragment) findFragment).getNavController();
}
Fragment primaryNavFragment = findFragment.getParentFragmentManager()
.getPrimaryNavigationFragment();
if (primaryNavFragment instanceof NavHostFragment) {
return ((NavHostFragment) primaryNavFragment).getNavController();
}
findFragment = findFragment.getParentFragment();
}
// Try looking for one associated with the view instead, if applicable
View view = fragment.getView();
if (view != null) {
return Navigation.findNavController(view);
}
throw new IllegalStateException("Fragment " + fragment
+ " does not have a NavController set");
}
It does more than just finding the controller. It traverses, creates fragment, creates views and throw an exception.
View binding just generates a binding class with all the views of your layout in it. It is not meant for finding the navigation controller of the app.

Here is my sample code, using view binding & navigation.
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.zeddigital.zigmaster.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/*
Use view binding in activities
Call the static inflate() method included in the generated binding class.
This creates an instance of the binding class for the activity to use.
Get a reference to the root view by either calling the getRoot() method or using Kotlin property syntax.
Pass the root view to setContentView() to make it the active view on the screen.*/
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
setSupportActionBar(binding.appBarMain.toolbar)
//val navController = findNavController(R.id.nav_host_fragment)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(setOf(
R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow), binding.drawerLayout)
setupActionBarWithNavController(navController, appBarConfiguration)
binding.navView.setupWithNavController(navController)
}
}

With help in this answer, i find a simple implementation
binding?.apply {
setContentView(root)
setSupportActionBar(toolbar)
navController = (supportFragmentManager
.findFragmentById(fragmentHost.id) as NavHostFragment)
.navController
setupActionBarWithNavController(navController, appBarConfiguration)
bottom.setupWithNavController(navController)
}

findNavController(R.id.nav_host_fragment) should come after attaching view to activity like this:
setContentView(binding.root)
val navController: NavController = findNavController(R.id.nav_host_fragment)
More Here

Related

Kotlin: Live data does not change Fragment UI when data changes

I am struggling to use Live data on an MVVM pattern. The app is supposed to:
Fetch data from an API (which it does correctly)
Store that data in the Live data object from the ViewModel
Then the fragment calls the Observer method to fill the recyclerView.
The problem comes in point 3, it does nothing, and I cannot find the solution.
Here is the relevant code. (If I'm missing something, I will try to answer as quickly as possible)
Main Activity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: SharedViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Custom button to fetch data from api and log the Live Data value.
binding.refreshFab.setOnClickListener {
viewModel.fetchPlayerData()
Log.d("gabs", "${viewModel.livePlayerlist.value}")
}
}
}
ViewModel:
class SharedViewModel(app: Application): AndroidViewModel(app) {
// val playerDao = LaRojaDB.getDatabase(app).playerDao()
lateinit var playerList: Players
val livePlayerlist: MutableLiveData<MutableList<Players.PlayersItem>> by lazy {
MutableLiveData<MutableList<Players.PlayersItem>>()
}
fun fetchPlayerData() {
CoroutineScope(Dispatchers.IO).launch {
val response = MyService.getLaRojaService().getAllPlayers()
withContext(Dispatchers.Main) {
if (response.isSuccessful) {
val body = response.body()
if(!body.isNullOrEmpty()){
playerList = body
val playerArrayList = mutableListOf<Players.PlayersItem>()
playerList.forEach {
playerArrayList.add(it)
}
livePlayerlist.value = playerList
}
}
}
}
}
}
The fragment that displays the recycler view: (Fragment is already showing, I set up a textView as a title to make sure since I'm new using fragments as well.)
class PlayerListFragment : Fragment() {
private var _binding: FragmentPlayerListBinding? = null
private val binding get() = _binding!!
private val model: SharedViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentPlayerListBinding.inflate(inflater, container, false)
binding.rvPlayerList.layoutManager = LinearLayoutManager(activity)
----> // This is the observer that does not update the UI** <----
model.livePlayerlist.observe( viewLifecycleOwner, {
binding.rvPlayerList.adapter = PlayerAdapter(it)
})
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_player_list, container, false)
}
}
Thank you all in advance, hope I can finally learn what is causing the issue!
I think you don't need to switch Coroutine contexts. A few changes I'd expect if I were reviewing this code:
This should all be in the same IO context. You then postValue to your liveData.
fun fetchPlayerData() {
viewModelScope.launch(Dispatchers.IO) {
val xx = api.fetch()
...
_playerState.postValue(xx) //see below
}
}
Additionally, it's preferred not to expose mutable state, so your ViewModel should not expose the MutableLiveData (which shouldn't really be lazy). But it's also better to encapsulate the state in a sealed class:
//delete this
val livePlayerlist: MutableLiveData<MutableList<Players.PlayersItem>> by lazy {
MutableLiveData<MutableList<Players.PlayersItem>>()
}
Should be: (names are just pseudo code, I have no idea what this code is about)
sealed class PlayerDataState {
data class ListAvailable(data: List<Players.PlayersItem>>): PlayerDataState
object Loading(): PlayerDataState
}
And your new LiveData:
private val _playerState = MutableLiveData<PlayerDataState>()
val playerState: LiveData<PlayerDataState>() get() = _playerState
Finally when observing from the UI, you just...
model.playerState.observe(viewLifecycleOwner, {
when (it) {
is Loading -> ...
is ListAvailable -> binding.rvPlayerList.adapter = PlayerAdapter(it.data)
}
}

Struggling to transition code from AppcompatActivity to Fragment. Unable to use setSupportActionBar(toolbar)

I'm trying to transition from using AppcomatActivity to Fragment, because I'm updating my app but I've run into a problem. I get a "Unresolved reference: supportFragmentManager" and "Unresolved reference: setSupportActionBar"(That's all the Logcat shows me) when I try to run my app. Now I chose to switch to using fragments, because I also want to change the apps UI, usage is quicker than the previous version. Anyway here's my code:
Recorder Fragment
package com.khumomashapa.notes.fragments
import android.os.Build
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toolbar
import androidx.annotation.RequiresApi
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import com.astuetz.PagerSlidingTabStrip
import com.khumomashapa.notes.R
import kotlinx.android.synthetic.main.toolbar3.*
class RecorderFragment : Fragment() {
private var tabs: PagerSlidingTabStrip? = null
private var pager: ViewPager? = null
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.title = "Recorder";
pager = pager?.findViewById<View>(R.id.pager) as ViewPager
pager!!.adapter = MyAdapter(supportFragmentManager)
tabs = tabs?.findViewById<View>(R.id.tabs) as PagerSlidingTabStrip
tabs!!.setViewPager(pager)
val toolbar = toolbar?.findViewById<View>(R.id.toolbar) as Toolbar
toolbar.popupTheme = R.style.ThemeOverlay_AppCompat_Light
setSupportActionBar(toolbar)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_recorder, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
inner class MyAdapter(fm: FragmentManager?) : FragmentPagerAdapter(
fm!!
) {
private val titles = arrayOf(
getString(R.string.tab_title_record),
getString(R.string.tab_title_saved_recordings)
)
override fun getItem(position: Int): Fragment {
when (position) {
0 -> {
return RecordFragment.newInstance(position)
}
1 -> {
return FileViewerFragment.newInstance(position)
}
}
return null!!
}
override fun getCount(): Int {
return titles.size
}
override fun getPageTitle(position: Int): CharSequence? {
return titles[position]
}
}
companion object {
private val LOG_TAG = RecorderFragment::class.java.simpleName
}
}
The purpose of this class is to show a view pager that can switch between to other fragments I've already created. I was able to fix the other errors relating to this like the "Unresolved reference for findViewById" and the "MyAdapter class".
For a Fragment, there are actually two relevant fragment managers. Deciding which one to use depends on your use case.
The Child Fragment Manager
A fragment has a child fragment manager which is responsible for managing its child/nested fragments. You can obtain this with:
Fragment.getChildFragmentManager()
The Parent's Fragment Manager
A fragment also holds reference to it's parent's fragment manager. If the fragment is a direct child of an activity then his represents the activities fragment manager. Otherwise if the fragment is a child of another fragment, it represents the child fragment manager of the parent fragment. This can be obtained with:
Fragment.getParentFragmentManager()
Note that although Fragment has the method Fragment.getFragmentManager(), this is deprecated in favour of Fragment.getParentFragmentManager() so it shouldn't be used.
You can also technically get the activities fragment manager regardless by obtaining a reference to the fragment's activity with Fragment.getAcitivity() and then calling Activity.getSupportFragmentManager(). But generally the parent fragment manager is more useful and clear.

Unconditional layout inflation from view adapter. kotlin

I am getting following warning in Android Studio:
"Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling." on inflater.inflate(R.layout.animal_ticket, null) line.
How do I fix the warning? I am not able to find solution for that problem.
Thanks!
override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {
val animal = listOfAnimals[p0]
val inflater = LayoutInflater.from(context)
var holder = ViewHolder()
val myView = inflater.inflate(R.layout.animal_ticket, null)
myView.tvName.text = animal.name!!
myView.tvDes.text = animal.description!!
myView.jvAnimalImage.setImageResource(animal.image!!)
return myView
}
If there was previously another View of the same view type (getItemViewType(position: Int) returned the same value) that scrolled off screen, the list view may pass in that instance as the second parameter to getView(). It will be faster to reuse that view than to inflate a new one.
You should also use the ViewHolder to cache things about the view, such as the relatively expensive findViewById(). You can attach it to and retrieve it from the view by tag.
override fun getView(position: Int, convertView: View?, parent: ViewGroup?) {
val animal = listOfAnimals[position]
val myView = convertView ?:
LayoutInflater.from(context).inflate(R.layout.animal_ticket, parent, false)
val holder = myView.tag as? ViewHolder ?: ViewHolder(myView)
myView.tag = holder
holder.tvName.text = animal.name!! // etc
}
class ViewHolder(view: View) {
val tvName: TextView = view.name // etc
}
For any RecyclerView, you need your own ViewHolder class that has all of the views listed inside animal_ticket.
Basically, it works like this:
1) Create ViewHolder that 'holds' all of the views of the item you want to display;
2) Bind the ViewHolder to the RecyclerView and assign values to the views inside;
Here is a example adapter I wrote :
class MyActivity : Activity() {
//users is the list we're going to use to get information for the views
val users = ArrayList<User>()
//...getting user information
//.. your activity stuff here
//Creating our adapter
/*
Note that to extend the RecyclerView.Adapter, you need to specify a
ViewHolder. The code becomes much easier to manage if you just put the
ViewHolder inside your adapter.
*/
inner class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
//This is the view holder
inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//Here you declare your views and get them from the itemView
//The itemView is one that is passed each time to the RecyclerView
//(the items inside your XML layout file)
internal val userImageView = itemView.findViewById(R.id.userImage)
internal val userFullName = itemView.findViewById(R.id.userName)
}
//This is where you return your own ViewHolder with your layout
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
//user_list_item.xml is below
val itemView = layoutInflater.inflate(R.layout.user_list_item, parent, false))
return MyViewHolder(itemView)
}
//In here is where you want to set your values for the views
override fun onBindViewHolder(holder: SentRequestViewHolder, position: Int) {
val currentUser = users[position]
holder.userImage.drawable = currentUser.drawable
holder.userFullName.text = currentUser.name
}
//You must override this method as well for the adapter to work properly
override fun getItemCount() = users.size
}
You have to override these methods when using a RecyclerView.Adapter
Here is the user_list_item.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp">
<ImageView
android:id="#+id/userImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/userName"
android:layout_below="#id/userImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>

dispatch custom event don't work in haxe

i have a weird problem...
i try to build a button that onClick doing something(i dont use in regular event because i need to transfer a data with the event).
i built a UIButon class and create an instance of him in the parent class.
i create a custom Event class:
package ;
import openfl.events.Event;
/**
* ...
* #author Michael
*/
class ChangeWinEvent extends Event
{
public static inline var CHANGE_WINDOW:String = "changeWindow";
public var _winToClose:String;
public function new(name:String, winToClose:String, bubbles:Bool=false, cancelable:Bool=false)
{
super(type, bubbles, cancelable);
_winToClose = winToClose;
}
}
and i dispatch CHANGE_WINDOW event like this:
dispatchEvent(new ChangeWinEvent(ChangeWinEvent.CHANGE_WINDOW,"LoginWin"));
and listen to this event in parent class:
_loginBtn.addEventListener(ChangeWinEvent.CHANGE_WINDOW, handleChangeWindows);
thank you helpers!
michael
You also have to override the clone method. Take a look at this custom event class i'm currently using:
/**
* Custom button event used for communication
* between button classes and their respective
* views.
* #author Tiago Ling Alexandre
*/
class ButtonEvent extends Event
{
public static var ACTIVATE:String = "Activate";
public var data:Dynamic;
public function new(type:String, data:Dynamic, bubbles:Bool = false, cancelable:Bool = false)
{
super(type, bubbles, cancelable);
this.data = data;
}
override public function clone():Event
{
return new ButtonEvent(type, bubbles, cancelable);
}
}
The only difference from your class is the clone() method. That's the missing piece.
Regards,
super(type, bubbles, cancelable);
...apparently takes type variable from type instance field (as you have constructor parameter named name, not type), so it is null and you haven't registered any listener for null event type.

Does omnifaces have pagination related components?

I searched in omnifaces' showcase without success for any component that helps with JSF (datatable) real pagination.
Do you guys know if there is any?
It would be a great feature.
I worked a bit the aproach taken here https://rogerkeays.com/blog/paging-large-data-sets-with-a-lazylist and came with the code below.
It's very generic and could be used with any JSF component library or persistence API.
The developer must implement the lazy list in his JSF managed bean (backing bean), which has access to the business facade or entity manager to do the queries.
import java.util.AbstractList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class AbstractLazyList extends AbstractList {
/**
* Current page cache.
*/
private final Map<Integer, Object> currentPage = new HashMap<Integer, Object>();
/**
* Page size. Default 50.
*/
private int pageSize = 50;
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
#Override
public Object get(int index) {
if (currentPage.containsKey(index)) {
return currentPage.get(index);
}
queryNewPage(index);
return currentPage.get(index);
}
/**
* Clears cache and queries new page's itens.
*
* #param firstItemIndex
* first item index
*/
private void queryNewPage(int firstItemIndex) {
currentPage.clear();
final List<Object> pageResults = queryNextPage(firstItemIndex, pageSize);
for (int j = 0; j < pageResults.size(); j++) {
currentPage.put(firstItemIndex + j, pageResults.get(j));
}
}
#Override
public int size() {
return queryResultsSize();
}
/**
* Queries next page itens.
*
* #param firstRowIndex
* first row index
* #param pageSize
* maximum resuts of the query
* #return list of results
*/
protected abstract List<Object> queryNextPage(int firstRowIndex,
int pageSize);
/**
* Counts the total results of the query.
*
* #return query's total results
*/
protected abstract int queryResultsSize();
}
}
Maybe an aproach like this could fit in omnifaces.
OmniFaces is not geared to visually oriented UI components, but more to utilities which do its work "behind the scenes" and can be used in combination with any other JSF component library.
Consider looking at visually oriented UI component libraries such as PrimeFaces and RichFaces. They offer data table components with pagination possibilities.
PrimeFaces <p:dataTable> showcase
RichFaces <rich:dataTable>+<rich:dataScroller> showcase

Resources