I'm currently writing an app with the new (to me) navigation component. I've got the basics down with a single navigation graph to navigate around my app, I've got a fragment with a BottomNavigationView in which has 3 seperate fragments, I've managed to update this to use the navigation component (as far as my problem) using the menu with ids that match the navigation items. My fragments which all previously used newInstance methods to pass a bundle to the onCreate are obviously now not used but I still need to pass a bundle to my fragments.
I haven't been able to find any examples of this being done, as the fragments are implicitly created.
My code is structured as ClientFragment which is the host fragment for the navigation drawer etc which is;
class ClientFragment : Fragment() {
private val viewModel: ClientViewModel by viewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_client, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.client = arguments?.getParcelable(ARG_CLIENT)!!
toolbar_client.title = viewModel.client.name
toolbar_client.setNavigationOnClickListener { Navigation.findNavController(view).navigateUp() }
}
}
This class previously held on onclick listener to my fragments, with a newInstance method which tool viewModel.client.
My fragments in the nav_graph are all similar. The first fragment;
class ClientDetailsFragment : Fragment() {
private val viewModel: ClientViewModel by viewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_client_details, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// viewModel.client = arguments?.getParcelable(ARG_CLIENT)!!
initClientDetails()
}
private fun initClientDetails() {
// text_client_details_name.text = viewModel.client.name
// text_client_details_account_number.text = viewModel.client.accountNumber
// text_client_details_mobile_number.text = viewModel.client.mobileNumber
// text_client_details_landline_number.text = viewModel.client.landlineNumber
// text_client_details_email.text = viewModel.client.email
// text_client_details_address.text = "NOT YET IMPLEMENTED"
//
// text_client_description_body.text = viewModel.client.description
// text_client_system_details_body.text = viewModel.client.systemDetails
}
}
The app crashes on the commented out line;
// viewModel.client = arguments?.getParcelable(ARG_CLIENT)!!
My navigation graph and menu are;
nav graph;
<?xml version="1.0" encoding="utf-8"?>
<navigation 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/client_nav_graph"
app:startDestination="#id/clientDetailsFragment">
<fragment
android:id="#+id/clientCustomersFragment"
android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientCustomersFragment"
android:label="ClientCustomersFragment"
tools:layout="#layout/fragment_client_customers" />
<fragment
android:id="#+id/clientDetailsFragment"
android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientDetailsFragment"
android:label="ClientDetailsFragment"
tools:layout="#layout/fragment_client_details"/>
<fragment
android:id="#+id/clientJobHistoryFragment"
android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientJobHistoryFragment"
android:label="ClientJobHistoryFragment"
tools:layout="#layout/fragment_client_job_history" />
</navigation>
menu;
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/clientDetailsFragment"
android:icon="#drawable/ic_launcher_foreground"
android:title="Details"/>
<item
android:id="#+id/clientJobHistoryFragment"
android:icon="#drawable/ic_launcher_foreground"
android:title="Job History"/>
<item
android:id="#+id/clientCustomersFragment"
android:icon="#drawable/ic_launcher_foreground"
android:title="Customers"/>
</menu>
I have found that you can add arguments to the navigation graph, but have found nothing about where to put them for this specific scenario, I'm also aware of being able to manual add bundles when navigating using .navigate.
Is there a way for me to set in my ClientFragment the arguments for each of these fragments to be
viewModel.client
Update:
My argument issue was solved by using a view model that's shared between all of the fragments in the BottomNavigationView (I realised this as I was typing the issue out to my friend) and the navigation itself I added this to the ClientFragment;
bottom_nav_client.setupWithNavController(
Navigation.findNavController(
view.findViewById<View>(R.id.fl_client_nav_container)
)
)
and my xml for fragment_client;
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar_client"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#color/colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="?attr/NavigationBackIconLight"
app:titleTextColor="#color/white" />
<fragment
android:id="#+id/fl_client_nav_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#id/bottom_nav_client"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/toolbar_client"
app:navGraph="#navigation/client_nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_nav_client"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="?android:attr/windowBackground"
app:itemBackground="#color/colorPrimary"
app:itemIconTint="#drawable/bottom_nav_color"
app:itemTextColor="#color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/client_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
This is combined with the same navigation graph and menu as shown above.
The Codelabs referred to in the accepted answer don't mention passing arguments to fragments in the BottomNavigationView.
Override the OnNavigationItemSelectedListener set by the setupWithNavController() with a custom one:
val args = Bundle()
bottomNavigationView.setupWithNavController(navController)
bottomNavigationView.setOnNavigationItemSelectedListener { item ->
navController.navigate(item.itemId, args)
true
}
if you are using NavigationComponent :
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
NavigationUI.onNavDestinationSelected(menuItem, navController);
}
you use this function, inside onNavDestiationSelected function you can see that navigation component passes null to args, so if you wanna pass argument to bottomNavigation fragments, simply you can write navigation function byyourselft and the only change is pass your arguments. as follow:
create a package level function :
fun onNavDestinationSelected(item: MenuItem, navController: NavController, args: Bundle?): Boolean {
val builder = NavOptions.Builder().setLaunchSingleTop(true).setRestoreState(true)
if (
navController.currentDestination!!.parent!!.findNode(item.itemId)
is ActivityNavigator.Destination
) {
builder.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim)
} else {
builder.setEnterAnim(R.animator.nav_default_enter_anim)
.setExitAnim(R.animator.nav_default_exit_anim)
.setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
.setPopExitAnim(R.animator.nav_default_pop_exit_anim)
}
if (item.order and Menu.CATEGORY_SECONDARY == 0) {
builder.setPopUpTo(
navController.graph.findStartDestination().id,
inclusive = false,
saveState = true
)
}
val options = builder.build()
return try {
// TODO provide proper API instead of using Exceptions as Control-Flow.
navController.navigate(item.itemId, args, options)
// Return true only if the destination we've navigated to matches the MenuItem
navController.currentDestination?.hierarchy?.any { it.id == item.itemId } == true
} catch (e: IllegalArgumentException) {
false
}
}
and use it inside onNavigationItemSelected function as follow :
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
OnNavDestinationSelectKt.onNavDestinationSelected(menuItem, navController, yourArgument);
}
The Navigation Architecture Component documentation shows how to define destination arguments, in your concrete case you should create a custom Parcelable class (i.e. Client) and include it as an argument of the corresponding fragment.
<fragment
android:id="#+id/clientCustomersFragment"
android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientCustomersFragment"
android:label="ClientCustomersFragment"
tools:layout="#layout/fragment_client_customers" >
<action
android:id="#+id/client_to_details"
app:destination="#+id/clientDetailsFragment" />
</fragment>
<fragment
android:id="#+id/clientDetailsFragment"
android:name="com.management.engineering.alarm.alarmengineermanagement.features.client.ClientDetailsFragment"
android:label="ClientDetailsFragment"
tools:layout="#layout/fragment_client_details">
<argument
android:name="client"
app:argType="com.management.engineering.alarm.alarmengineermanagement.features.client.Client" />
</fragment>
The 'androidx.navigation.safeargs' gradle plugin will generate the classes ClientToDetails and ClientDetailsFragmentArgs which can be used to pass.retrieve the parameter client.
Source
val client: Client = TODO()
val navController: NavController = TODO()
navController.navigate(ClientToDetails(client))
Destination
val client = ClientDetailsFragmentArgs.fromBundle(arguments).client
This codelabs exactly what you want to do :
https://codelabs.developers.google.com/codelabs/android-navigation/index.html?index=..%2F..index#0
Docs : https://developer.android.com/topic/libraries/architecture/navigation/
Related
I'm trying to make a simple RecyclerView with expandable items using Kotlin. Basically a list of bins which expand out to show descriptions of what should go in them. I tried to follow the Android Studio Recycler View example as closely as possible. The problem is when I expand each item, some artefacts would show.
Unexpanded view
Expanded view
Note: The expanded view is the result of pressing on "Recycling Bin", the artefact is made up of the bin's description text and also "Garden Waste Bin" somehow being duplicated.
Below is my implementation
Adapter class
class BinAdapter(private val dataSet: List<Bin>) : RecyclerView.Adapter<BinViewHolder>() {
var expandedPosition = -1
var previousExpandedPosition = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BinViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.bin_row_item, parent, false)
return BinViewHolder(view)
}
override fun onBindViewHolder(viewHolder: BinViewHolder, position: Int) {
viewHolder.itemView.isActivated
viewHolder.titleView.text = dataSet[position].name
viewHolder.descriptionView.text = dataSet[position].description
val isExpanded = position == expandedPosition
viewHolder.descriptionView.visibility = if (isExpanded) View.VISIBLE else View.GONE
viewHolder.itemView.isActivated = isExpanded
if (isExpanded) {
previousExpandedPosition = position
}
viewHolder.itemView.setOnClickListener {
expandedPosition = if (isExpanded) -1 else position
notifyItemChanged(previousExpandedPosition)
notifyItemChanged(position)
}
}
override fun getItemCount() = dataSet.size
Fragment class
class BinLookupFragment : Fragment() {
private var _binding: FragmentBinLookupBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentBinLookupBinding.inflate(inflater, container, false)
binding.binRecyclerView.adapter = BinAdapter(BinList(resources))
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
ViewHolder class
class BinViewHolder(itemView: View) : ViewHolder(itemView) {
val titleView: TextView
val descriptionView: TextView
init {
titleView = itemView.findViewById(R.id.titleView)
descriptionView = itemView.findViewById(R.id.descriptionView)
}
}
Row item layout
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/titleView"
android:layout_width="#dimen/bin_list_width"
android:layout_height="#dimen/bin_list_item_title_height"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="#+id/descriptionView"
android:layout_width="#dimen/bin_list_width"
android:layout_height="#dimen/bin_list_item_description_height"
app:layout_constraintTop_toBottomOf="#id/titleView"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
fragment layout
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/bin_recycler_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="200dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Has anyone come across this before or have some debugging tips?
I have an app which has one main activity and 3 fragments. Let's name them A, B and C as shown below:
Fragment A has a RecyclerView. When i click on an item in the
RecyclerView, it passes an Account object in safe args to the
Fragment B where it displays the data.
In Fragment B, the user can then press the edit button to edit the Account object, which then
navigates him/her to Fragment C.
In fragment C, the user can either press the back button to cancel
the modification and return to Fragment B, or the user can modify and
press save to go back to Fragment A.
The problem here is that, if after pressing the edit button, the user perform some modifications and then press the back button without saving, it still modifies the object temporarily (temporarily because if i close the app and then open it again, the object resets to its original state.).
Below is my code (this is a simple code just for the sake of reproducing the issue):
Fragment A.kt
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/Layout_Fragment_Account"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/RecyclerView_Account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="#{ViewModel.loadingStatus==List.LIST ? View.VISIBLE : View.GONE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment A.kt
private fun subscribeAccounts(accounts: List<Account>) {
val adapter = AccountAdapter(
/* The click listener to handle account on clicks */
AccountClickListener {
navigateTo(AccountFragmentDirections.actionFragmentAccountToFragmentViewAccount(it))
},
/* The click listener to handle popup menu for each accounts */
AccountOptionsClickListener { view, Account ->
//View Popup
}
)
binding.RecyclerViewAccount.apply {
/*
* State that layout size will not change for better performance
*/
setHasFixedSize(true)
/* Bind the layout manager */
layoutManager = LinearLayoutManager(requireContext())
/* Bind the adapter */
this.adapter = adapter
}
/* Submits the list for displaying */
adapter.submitList(accounts)
}
Fragment B.xml
<layout 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">
<data>
<variable
name="Account"
type="...Account" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/Layout_Credential_View"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/Account_Logo"
errorResource="#{#drawable/ic_account_placeholder}"
imageUrl="#{Account.logoUrl}"
loadingResource="#{#drawable/ic_image_loading}"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="#drawable/ic_account_placeholder" />
<TextView
android:id="#+id/Account_Name"
style="#style/Locky.Text.Title5.Name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:text="#{Account.entryName}"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/Account_Logo"
tools:text="This can be a very very very long title toooooo" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Fragment B.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
/* Binds the UI */
_binding = FragmentViewAccountBinding.inflate(inflater, container, false)
/* Instantiate the view model */
_viewModel = ViewModelProvider(this).get(ViewAccountViewModel::class.java)
/* Bind lifecycle owner to this */
binding.lifecycleOwner = this
/*
* Fetch the account object from argument
* Then bind account object to layout
*/
val account = ViewAccountFragmentArgs.fromBundle(requireArguments()).accountToVIEW
binding.account = account
_account = account
/* Returns the root view */
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_credentials_actions, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem) =
when (item.itemId) {
R.id.Action_Duplicate -> {
navigateTo(
ViewAccountFragmentDirections.actionFragmentViewAccountToFragmentAddAccount(
_account.apply {
this.id = 0
})
)
true
}
R.id.Action_Edit -> {
navigateTo(
ViewAccountFragmentDirections.actionFragmentViewAccountToFragmentAddAccount(
_account
)
)
true
}
else -> false
}
}
Fragment C.xml
<layout 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">
<data>
<import type="...Constants" />
<variable
name="ViewModel"
type="...AddAccountViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp">
...
<!--
**************** Require Text Fields ****************
-->
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/Account_Name"
style="#style/Locky.TextBox.Default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="#string/field_account_name"
app:endIconMode="clear_text"
app:helperText="#string/label_required"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/Barrier_Logo"
app:startIconDrawable="#drawable/ic_profile">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords"
android:text="#={ViewModel.entryName}" />
</com.google.android.material.textfield.TextInputLayout>
...
</LinearLayout>
</layout>
Fragment C.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentAddAccountBinding.inflate(inflater, container, false)
/* Binds the UI */
_binding = FragmentAddAccountBinding.inflate(inflater, container, false)
/* Instantiate the view model */
_viewModel = ViewModelProvider(this).get(AddAccountViewModel::class.java)
/* Bind view model to layout */
binding.viewModel = viewModel
/* Bind lifecycle owner to this */
binding.lifecycleOwner = this
/*
* Fetch the account object from argument
* And set it to view model for two way binding
*/
val account = AddAccountFragmentArgs.fromBundle(requireArguments()).accountToADD
viewModel.setAccount(account)
/* Returns the root view */
return binding.root
}
Fragment C view model
class AddAccountViewModel(application: Application) : ObservableViewModel(application) {
/**
* Bindable two-way binding
**/
private lateinit var _account: Account
var entryName: String
#Bindable get() {
return _account.entryName
}
set(value) {
_account.entryName = value
notifyPropertyChanged(BR.entryName)
}
var logoUrl: String?
#Bindable get() {
return _account.logoUrl
}
set(value) {
_account.logoUrl = value ?: ""
notifyPropertyChanged(BR.logoUrl)
}
internal fun setAccount(account: Account?) {
this._account = account ?: Account()
}
}
navigation.xml
<fragment
android:id="#+id/Fragment_A"
android:name="...AccountFragment"
android:label="Accounts"
tools:layout="#layout/fragment_account">
<action
android:id="#+id/action_Fragment_Account_to_BottomSheet_Fragment_Account_Filter"
app:destination="#id/BottomSheet_Fragment_Account_Filter" />
<action
android:id="#+id/action_Fragment_Account_to_Fragment_View_Account"
app:destination="#id/Fragment_View_Account" />
</fragment>
<fragment
android:id="#+id/Fragment_B"
android:name="...ViewAccountFragment"
android:label="View Account"
tools:layout="#layout/fragment_view_account">
<action
android:id="#+id/action_Fragment_View_Account_to_Fragment_Add_Account"
app:destination="#id/Fragment_Add_Account" />
<argument
android:name="ACCOUNT_toVIEW"
app:argType="....Account" />
</fragment>
<fragment
android:id="#+id/Fragment_C"
android:name="...AddAccountFragment"
android:label="Add Account"
tools:layout="#layout/fragment_add_account">
<action
android:id="#+id/action_Fragment_Add_Account_to_BottomSheet_Fragment_Account_Logo"
app:destination="#id/BottomSheet_Fragment_Account_Logo" />
<argument
android:name="ACCOUNT_ToADD"
app:argType="...Account" />
</fragment>
Below is a demonstration of the issue:
I've tried a quick hack where i save an instance of an unmodified account object and before the user leaves the fragment i reset the changes by assigning each variable but this is not efficient. I think i am doing something wrong here with the safe args?
Can someone please help me. I really can't figure this one out. Thank you
I am trying to make a Todo app and I have done the Room part and able to store data now I want to display the data in the form of Recycler View but I am not getting how to write the adapter class corresponding to it. I looked for it in different sites I never got any satisfying answer.
**TodoFragViewModel.kt""
class TodofragViewModel(
val database: TodoDao, applicaltion: Application
): AndroidViewModel(applicaltion) {
// TODO: Implement the ViewModel
/**
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
*/
private var viewModelJob = Job()
/**All coroutines can be cancelled by viewmodelJob.cancel() and Dispatcher.main is byDefault choice
*/
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private val currenctTodo = MutableLiveData<Todo?>()
private val allTodo = database.getAllTodo()
init{
intializeThisTodo()
}
private fun intializeThisTodo(){
uiScope.launch {
currenctTodo.value=getFromDatabase()
}
}
private suspend fun getFromDatabase(): Todo? {
return withContext(Dispatchers.IO){
val info =database.getCurrentTodo()
info
}
}
private suspend fun insert(thisTodo: Todo) {
withContext(Dispatchers.IO) {
database.insert(thisTodo)
Log.i("Database","${database.getCurrentTodo()?.description} and ${database.getCurrentTodo()?.time}")
}
}
fun onAdded(time:String,description:String) {
uiScope.launch {
val thisTodo = Todo(time,description)
insert(thisTodo)
currenctTodo.value=getFromDatabase()
}
}
/**
* Called when the ViewModel is dismantled.
* At this point, we want to cancel all coroutines;
* otherwise we end up with processes that have nowhere to return to
* using memory and resources.
*/
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
todo_recycler_view
<androidx.cardview.widget.CardView 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:layout_width="match_parent" android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/date_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/todo_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/date_text" />
</androidx.constraintlayout.widget.ConstraintLayout>
TodoFrag.kt
class todofrag : Fragment() {
companion object {
fun newInstance() = todofrag()
}
private lateinit var viewModel: TodofragViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.todofrag_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val application = requireNotNull(this.activity).application
val dataSource= TodoDatabase.getInstance(application)?.InformationDatabaseDao
val viewModelFactory = dataSource?.let { TodoViewModelFactory(it, application) }
val viewModel=ViewModelProviders.of(this,viewModelFactory).get(TodofragViewModel::class.java)
add_button.setOnClickListener{
val currentDate: String = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).format(Date())
val currentTime: String = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())
val time:String="${currentDate} \n ${currentTime}"
viewModel.onAdded(time,todo_text.text.toString())
}
}
}
Please let me know if any other files are added. By the way, I tried to use card view so that it looked good.
The developer documentation explains it pretty well.
This might not be perfectly suited for what you need, but it should be a good start. Specifically, I don't know all the fields for your Todo class, so make sure you account for those in this code.
Basically, you'll want to have a ViewHolder that represents your CardView
class TodoViewHolder(convertView: View) : RecyclerView.ViewHolder(convertView) {
val dateText = convertView.findViewById(R.id.date_text)
val description = convertView.findViewById(R.id.todo_description)
// whatever else you need access to
}
And you'll want to use DiffUtil for a better user experience. This allows for some animations when things in the list change, such as removing an item, editing an item, or adding an item.
private class TodoDiffCallback : DiffUtil.ItemCallback<Todo>() {
override fun areItemsTheSame(oldItem: Todo, newItem: Todo) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Todo, newItem: Todo) =
oldItem.dateText == newItem.dateText && oldItem.description == newItem.description
}
You'll want to extend ListAdapter and override its methods. onCreateViewHolder creates an instance of your TodoViewHolder for each view that is seen and onBindViewHolder allows you to add behavior to each item in the list. It is worth noting that you can pass parameter into the adapter in case you need to.
class MyListAdapter : ListAdapter<Todo, TodoViewHolder>(TodoDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = TodoViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.todo_recycler_view, parent, false))
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
val todo = getItem(position)
holder.dateText = todo.dateText
holder.description = todo.description
// add whatever click listener and other stuff you need
}
}
In your fragment, when you access your RecyclerView, just add an instance of the adapter if it's null.
if (recyclerView.adapter == null) {
recyclerView.adapter = TotoListAdapter()
}
And when you want to add data (that you have retrieved from Room or your API) to the adapter (in the fragment/activity), just do the following:
(recyclerView.adapter as? TodoListAdapter)?.submitList(data)
On a side note, make sure to clean up your style (you can use the Reformat Code command in the Code menu), and you would want to rename the todo_recycler_view to something like todo_view. You'll want to have a RecyclerView layout in your fragment layout.
I read some document about using recyclingview for activity. Now i try to use recycleview to my fragment. the problem is my fragment look empty when i execute.
fragment:
class KategoriFragment : Fragment() {
var araclarKategori = ArrayList<AracMarka>()
private lateinit var galleryViewModel: GalleryViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
veriKaynaginiDoldur()
galleryViewModel =
ViewModelProviders.of(this).get(GalleryViewModel::class.java)
var root = inflater.inflate(R.layout.fragment_category, container, false)
veriKaynaginiDoldur()
var rvarackategori = root.findViewById(R.id.rvarackategori) as RecyclerView
var MyAdapter = AracMarkaAdapter(araclarKategori)
rvarackategori.adapter = MyAdapter
/
return root
}
fun veriKaynaginiDoldur(): ArrayList<AracMarka> {
var aracLogo = arrayOf(R.drawable.opellogo, R.drawable.chevroletlogo)
var aracismi = resources.getStringArray(R.array.arabaisim)
for (i in 0 until min(aracismi.size, aracLogo.size)) {
var eklenecekaracKategori = AracMarka(aracismi[i], aracLogo[i])
araclarKategori.add(eklenecekaracKategori)
}
return araclarKategori
}
}
I create an adapter. I think there is no problem on it.
adapter:
class AracMarkaAdapter(tumKategori: ArrayList<AracMarka>) :
RecyclerView.Adapter<AracMarkaAdapter.AracMarkaViewHolder>() {
var araclar = tumKategori
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AracMarkaViewHolder {
var inflater = LayoutInflater.from(parent.context)
var arackategori = inflater.inflate(R.layout.arac_kategori, parent, false)
return AracMarkaViewHolder(arackategori)
}
override fun getItemCount(): Int {
return araclar.size
}
override fun onBindViewHolder(holder: AracMarkaViewHolder, position: Int) {
holder.aracismi.text=araclar.get(position).aracAdi
holder.aracLogo.setImageResource(araclar.get(position).aracLogo)
}
class AracMarkaViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var teksatirKategori= itemView
var aracismi= teksatirKategori.tvaracAdi
var aracLogo=teksatirKategori.img_arac_sembol
}
}
fragment xml:
<?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:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvarackategori"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
So, when i click button, fragment opens but it is empty. Do you have any idea about it?
After modifying the data in your list in veriKaynaginiDoldur(), you need to call myAdapter.notifyDataSetChanged() so it knows to rebind the data. Or you could call veriKaynaginiDoldur() before you instantiate your adapter.
Edit: Your other error is in your for loop within veriKaynaginiDoldur(). You are making a range using the size of the araclarKategori list when it is still zero.
Instead of
for (i in 0..araclarKategori.size - 1)
use
for (i in 0 until min(aracLogo.size, aracismi.size))
you have to call this veriKaynaginiDoldur() function after the below
lines of code below I have mentioned please check
var rvarackategori = root.findViewById(R.id.rvarackategori) as RecyclerView
var MyAdapter = AracMarkaAdapter(araclarKategori)
rvarackategori.adapter = MyAdapter
veriKaynaginiDoldur()
I am trying to learn how to use Kotlin/Anko.
I have gone thru the examples here and also cloned the template project and can understand how to do some basic stuff, but as an exercise I wanted to convert this simple activity (generated from a blank activity in Android Studio and converted to Kotlin) to use Anko as well. There are not a lot of examples around for Anko, most are just copies of what is on the above referenced github page.
Can someone demonstrate how to go about and convert the following into Anko DSL?
MainActivity.kt
import android.os.Bundle
import android.support.design.widget.FloatingActionButton
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolBar = findViewById(R.id.toolbar) as Toolbar
setSupportActionBar(toolBar)
val fab = findViewById(R.id.fab) as FloatingActionButton
fab.setOnClickListener { view -> Snackbar.make(view, "Replace this with your own action", Snackbar.LENGTH_LONG).setAction("Action", null).show() }
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.action_settings) {
println("settings clicked on ")
return true
}
return super.onOptionsItemSelected(item)
}
}
main_activity.xml
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_main" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
android:src="#android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context="com.gmail.npnster.mykotlinfirstproject.MainActivity"
tools:showIn="#layout/activity_main">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="#+id/hello"
/>
</RelativeLayout>
menu_main.xml
<menu 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"
tools:context="com.gmail.npnster.mykotlinfirstproject.MainActivity">
<item
android:id="#+id/action_settings"
android:orderInCategory="100"
android:title="#string/action_settings"
app:showAsAction="never" />
</menu>
You can use ankoView method to create Views without DSL methods inside DSL context.
For example, to create a NavigationView one can use
ankoView({ NavigationView(it) }) {
lparams(width = wrapContent, height = matchParent, gravity = Gravity.START)
// more initialization follows
}
This way you can instantiate FloatingActionButton and AppBarLayout, just call their constructors inside ankoView's first argument function. For your convenience, you can make yourself DSL-like functions like in the manual:
fun floatingActionButton(init: FloatingActionButton.() -> Unit) = ankoView({ FloatingActionButton(it) }, init)
Creating a Toolbar is even easier: there is a DSL toolbar method in org.jetbrains.anko.appcompat.v7.
When using Anko DSL, to include another layout, as you did with content_main, one can either use Anko include function or just write a function which will fill in a ViewGroup. You can use this template:
fun ViewGroup.myLayout() {
textView("123")
// more DSL code here
}
Then just call myLayout() inside some ViewGroup initializer.
I know it's a bit late answer, but I hope it helps someone. I did the layout this way (of course some styling is still needed):
class MainUI(val adapter: MainUIAdapter) : AnkoComponent<MainActivity> {
override fun createView(ui: AnkoContext<MainActivity>): View = with(ui) {
coordinatorLayout {
fitsSystemWindows = true
appBarLayout {
toolbar {
setTitleTextColor(Color.WHITE) // so far still needed
id = R.id.toolbar
}.lparams(width = matchParent, height = matchParent)
}.lparams(width = matchParent)
relativeLayout {
id = R.id.container
recyclerView { // just an example
id = R.id.recycler_view
adapter = this#MainUI.adapter
layoutManager = LinearLayoutManager(ctx)
}
}.lparams(width = matchParent, height = matchParent) {
behavior = ScrollingViewBehavior()
}
floatingActionButton {
onClick { doSomething() }
imageResource = R.drawable.ic_add_white_24dp // the plus sign
}.lparams {
gravity = Gravity.BOTTOM or Gravity.END
margin = dip(16)
}
}
}
}
and used in MainActivity like that:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MainUI(MainUIAdapter(people)).setContentView(this)
toolbar = find<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
}