error: [kapt] An exception occurred: android.databinding.tool.util.LoggedErrorException: Found data binding error(s):? - kotlin

I am learning databinding with mvvm but I am getting following errors I did not know what is the main problem.
DataBinderMapperImpl.java:9: error: cannot find symbol
import gahfy.net.databinding.ActivityPostListBindingImpl;
^
symbol: class ActivityPostListBindingImpl
location: package gahfy.net.databinding
error: [kapt] An exception occurred: android.databinding.tool.util.LoggedErrorException: Found data binding error(s):
[databinding] {"msg":"cannot find method getLoadingVisibility() in class gahfy.net.ui.post.PostListViewModel","file":"C:\\Users\\Edgar\\Documents\\MVVMPosts\\app\\src\\main\\res\\layout\\activity_post_list.xml","pos":[{"line0":22,"col0":37,"line1":22,"col1":68}]}
error: cannot find symbol
import gahfy.net.databinding.ActivityPostListBindingImpl;
^
symbol: class ActivityPostListBindingImpl
location: package gahfy.net.databinding
cannot find method getLoadingVisibility() in class gahfy.net.ui.post.PostListViewModel
what I have tried invalidate cache restart and rebuild and clean project it did not helped at all
below activity_post_list.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="gahfy.net.ui.post.PostListViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:mutableVisibility="#{viewModel.getLoadingVisibility()}" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/post_list"
android:layout_width="0dp"
android:layout_height="0dp"
app:adapter="#{viewModel.getPostListAdapter()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
below PostListActivity.kt
import android.os.Bundle
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import gahfy.net.R
import com.google.android.material.snackbar.Snackbar;
import gahfy.net.databinding.ActivityPostListBinding
class PostListActivity: AppCompatActivity() {
private lateinit var binding: ActivityPostListBinding
private lateinit var viewModel: PostListViewModel
private var errorSnackbar: Snackbar? = null
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_post_list)
binding.postList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
viewModel = ViewModelProviders.of(this).get(PostListViewModel::class.java)
viewModel.errorMessage.observe(this, Observer {
errorMessage -> if(errorMessage != null) showError(errorMessage) else hideError()
})
binding.viewModel = viewModel
}
private fun showError(#StringRes errorMessage:Int){
errorSnackbar = Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_INDEFINITE)
errorSnackbar?.setAction(R.string.retry, viewModel.errorClickListener)
errorSnackbar?.show()
}
private fun hideError(){
errorSnackbar?.dismiss()
}
}
below PostListViewModel.kt
class PostListViewModel:BaseViewModel(){
#Inject
lateinit var postApi: PostApi
private val loadingVisibility: MutableLiveData<Int> = MutableLiveData()
val errorMessage:MutableLiveData<Int> = MutableLiveData()
val errorClickListener = View.OnClickListener { loadPosts() }
private val postListAdapter: PostListAdapter = PostListAdapter()
private lateinit var subscription: Disposable
init{
loadPosts()
}
private fun loadPosts(){
subscription = postApi.getPosts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { onRetrievePostListStart() }
.doOnTerminate { onRetrievePostListFinish() }
.subscribe(
// Add result
{ result -> onRetrievePostListSuccess(result) },
{ onRetrievePostListError() }
)
}
private fun onRetrievePostListStart(){
loadingVisibility.value = View.VISIBLE
errorMessage.value = null
}
private fun onRetrievePostListFinish(){
loadingVisibility.value = View.GONE
}
private fun onRetrievePostListSuccess(postList:List<Post>){
postListAdapter.updatePostList(postList)
}
private fun onRetrievePostListError(){
errorMessage.value = R.string.post_error
}
override fun onCleared() {
super.onCleared()
subscription.dispose()
}
}
below BindingAdapters.kt
#BindingAdapter("mutableText")
fun setMutableText(view: TextView, text: MutableLiveData<String>?) {
val parentActivity:AppCompatActivity? = view.getParentActivity()
if(parentActivity != null && text != null) {
text.observe(parentActivity, Observer { value -> view.text = value?:""})
}
#BindingAdapter("mutableVisibility")
fun setMutableVisibility(view: View, visibility: MutableLiveData<Int>?) {
val parentActivity:AppCompatActivity? = view.getParentActivity()
if(parentActivity != null && visibility != null) {
visibility.observe(parentActivity, Observer { value -> view.visibility = value?:View.VISIBLE})
}
}
#BindingAdapter("adapter")
fun setAdapter(view: RecyclerView, adapter: RecyclerView.Adapter<*>) {
view.adapter = adapter
}
}

It's about your databinding usage in xml.
1.Your used variable must be public or a have public getter.
2.If you want use public variable just use it name (without get).
So you must make this changes in this lines.
private val loadingVisibility: MutableLiveData<Int> = MutableLiveData()
private val postListAdapter: PostListAdapter = PostListAdapter()
To
val loadingVisibility: MutableLiveData<Int> = MutableLiveData()
val postListAdapter: PostListAdapter = PostListAdapter()
And
app:mutableVisibility="#{viewModel.getLoadingVisibility()}"
app:adapter="#{viewModel.getPostListAdapter()}"
To
app:mutableVisibility="#{viewModel.loadingVisibility}"
app:adapter="#{viewModel.postListAdapter}"
BindAdapters
class BindAdapters {
companion object {
#BindingAdapter("mutableText")
fun setMutableText(view: TextView, text: MutableLiveData<String>?) {
val parentActivity: AppCompatActivity? = view.getParentActivity()
if (parentActivity != null && text != null) {
text.observe(parentActivity, Observer { value -> view.text = value ?: "" })
}
}
#BindingAdapter("mutableVisibility")
fun setMutableVisibility(view: View, visibility: MutableLiveData<Int>?) {
val parentActivity: AppCompatActivity? = view.getParentActivity()
if (parentActivity != null && visibility != null) {
visibility.observe(
parentActivity,
Observer { value -> view.visibility = value ?: View.VISIBLE })
}
}
#BindingAdapter("adapter")
fun setAdapter(view: RecyclerView, adapter: RecyclerView.Adapter<*>) {
view.adapter = adapter
}
}
}

Related

Saving checkbox states in RecyclerView inside Fragment using SharedPreferences or any other method

I'm trying to save checkbox states in RecyclerView inside Fragment to restore these preferences after exit from the app and loading it again.
I have a ConfigActivity for AppWidget in which there are fragments.
Inside of one of the fragments I have a RecyclerView which loads calendars available for the user from Calendar Provider. Based on selected calendars the appwidget will be loading the events from them. Selected calendars should be passed into the appwidget.
I've made saving states of the checkboxes while scrolling of the RecyclerView.
But I don't know how to save selected checkboxes in RecyclerView inside Fragment using SharedPreferences (saving for relaunching of the app).
My data class for calendar items:
data class CalendarItem(
val idCalendar: Long,
val displayNameCalendar: String?,
val accountNameCalendar: String?,
val colorCalendar: Int?
)
Item with checkbox in xml:
<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="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">
<ImageView
android:id="#+id/calendar_color"
android:layout_width="10dp"
android:layout_height="10dp"
android:src="#drawable/color_label_circle"
app:tint="#color/accent_color"
android:layout_alignParentStart="true"
android:layout_alignTop="#+id/text_display_name_calendar"
android:layout_alignBottom="#+id/text_display_name_calendar"/>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="#+id/text_display_name_calendar"
style="#style/basicText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginStart="24dp"
android:layout_marginEnd="4dp"
android:maxLines="1"
android:ellipsize="end"
android:gravity="start|center_vertical"
android:layoutDirection="rtl"
android:text="Display Name" />
<TextView
android:id="#+id/text_account_name"
style="#style/commentText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Account Name"
android:layout_marginEnd="4dp"
android:maxLines="1"
android:ellipsize="end"
android:layout_alignStart="#+id/text_display_name_calendar"
android:layout_below="#+id/text_display_name_calendar" />
</RelativeLayout>
My Fragment getting calendars:
class CalendarsEventsFragment : Fragment() {
// For permissions
private val PERMISSION_REQUEST_CODE = 101
// For RecyclerView - Calendars
private lateinit var calendarItemAdapter: CalendarItemAdapter
private lateinit var recyclerViewCalendars: RecyclerView
// Values for the calendars from the calendar content provider
private val EVENT_PROJECTION = arrayOf(
CalendarContract.Calendars._ID,
CalendarContract.Calendars.CALENDAR_DISPLAY_NAME,
CalendarContract.Calendars.ACCOUNT_NAME,
CalendarContract.Calendars.CALENDAR_COLOR
)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_calendars_events, container, false)
recyclerViewCalendars = view.findViewById(R.id.recyclerview_calendars)
// Setup permissions + start getCalendars
setupPermissionsGetCalendars()
return view
}
// Function to get and show calendars
private fun getCalendars() {
// Getting calendars from CalendarProvider
// In practice, should be done in an asynchronous thread instead of on the main thread
calendarItemAdapter = CalendarItemAdapter()
calendarItemAdapter.clearData()
val uri = CalendarContract.Calendars.CONTENT_URI
val cur: Cursor? = context?.contentResolver?.query(
uri,
EVENT_PROJECTION,
null,
null,
null
)
while (cur?.moveToNext() == true) {
val calId = cur.getLong(PROJECTION_ID_INDEX)
val displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX)
val accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX)
val color = cur.getInt(PROJECTION_CALENDAR_COLOR_INDEX)
calendarItemAdapter.pushData(
CalendarItem(
idCalendar = calId,
displayNameCalendar = displayName,
accountNameCalendar = accountName,
colorCalendar = color
)
)
}
cur?.close()
// Setup RecyclerView adapter
recyclerViewCalendars.let {
it.layoutManager = LinearLayoutManager(context)
it.adapter = calendarItemAdapter
}
}
// Function to check permission and make request for permission + start getCalendars
private fun setupPermissionsGetCalendars() {
if (checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) !=
PackageManager.PERMISSION_GRANTED
) {
requestPermissions(
arrayOf(Manifest.permission.READ_CALENDAR),
PERMISSION_REQUEST_CODE
)
} else {
getCalendars()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
PERMISSION_REQUEST_CODE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(
requireActivity(),
getText((R.string.toast_permission_granted)),
Toast.LENGTH_SHORT
).show()
getCalendars()
} else {
if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CALENDAR)) {
Toast.makeText(
requireActivity(),
getText((R.string.toast_permission_denied)),
Toast.LENGTH_SHORT
).show()
showUserRationale()
} else {
askUserOpenAppInfo()
}
}
}
}
}
private fun showUserRationale() {
AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.request_permission_rationale_title))
.setMessage(getString(R.string.request_permission_rationale_message))
.setPositiveButton("OK") { dialog, id ->
requestPermissions(
arrayOf(Manifest.permission.READ_CALENDAR),
PERMISSION_REQUEST_CODE
)
}
.create()
.show()
}
private fun askUserOpenAppInfo() {
val appSettingsIntent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", activity?.packageName, null)
)
if (activity?.packageManager?.resolveActivity(
appSettingsIntent,
PackageManager.MATCH_DEFAULT_ONLY
) == null
) {
Toast.makeText(
requireContext(),
getText(R.string.toast_permission_denied_forever),
Toast.LENGTH_SHORT
).show()
} else {
AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.request_permission_denied_forever_title))
.setMessage(getString(R.string.request_permission_denied_forever_message))
.setPositiveButton(getString(R.string.open_app_info_dialog_positive_button_text)) { dialog, id ->
startActivity(appSettingsIntent)
requireActivity().finish()
}
.setNegativeButton(getString(R.string.open_app_info_dialog_negative_button_text)) { dialog, id ->
requireActivity().finish()
}
.create()
.show()
}
}
}
My RecyclerView Adapter:
class CalendarItemAdapter() : RecyclerView.Adapter<CalendarItemAdapter.ViewHolder>() {
var data: MutableList<CalendarItem> = mutableListOf()
var checkedCalendarItems = SparseBooleanArray()
fun clearData() {
data.clear()
notifyDataSetChanged()
}
fun pushData(calendarItem: CalendarItem) {
data.add(calendarItem)
notifyDataSetChanged()
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val imageViewColor: ImageView = view.findViewById(R.id.calendar_color)
val displayNameOfCalendar: CheckBox = view.findViewById(R.id.text_display_name_calendar)
val accountName: TextView = view.findViewById(R.id.text_account_name)
init {
displayNameOfCalendar.setOnClickListener {
if(!checkedCalendarItems.get(adapterPosition, false)) {
displayNameOfCalendar.isChecked = true
checkedCalendarItems.put(adapterPosition, true)
} else {
displayNameOfCalendar.isChecked = false
checkedCalendarItems.put(adapterPosition, false)
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_calendar, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val datum = data[position]
datum.colorCalendar?.let {
holder.imageViewColor.setColorFilter(it)
}
holder.displayNameOfCalendar.text = datum.displayNameCalendar
holder.displayNameOfCalendar.isChecked = checkedCalendarItems.get(position, false)
holder.accountName.text = datum.accountNameCalendar
}
override fun getItemCount(): Int {
return data.size
}
}
Could you help me, please?
SharedPreferences can only store primitives and String arrays so you'll have to serialise your array somehow. Probably the easiest way is to just get all the indices of the checked items and throw them in a string. And when you pull that back out, split them up and set those to true.
You should probably handle this in the adapter, since really it's an internal implementation detail that only the adapter needs to know about. Something like this maybe:
class CalendarItemAdapter() : RecyclerView.Adapter<CalendarItemAdapter.ViewHolder>() {
var checkedCalendarItems = SparseBooleanArray()
fun saveState(prefs: SharedPreferences) {
// make a list of all the indices that are set to true, join them as a string
val checkedIndices = checkedCalendarItems
.mapIndexedNotNull {index, checked -> if (checked) index else null }
.joinToString(SEPARATOR)
prefs.edit { putString(KEY_CHECKED_INDICES, checkedIndices) }
}
fun restoreState(prefs: SharedPreferences) {
// reset the array - we're clearing the current state
// whether there's anything stored or not
checkedCalendarItems = SparseBooleanArray()
// grab the checked indices and set them - using null as a "do nothing" fallback
val checkedIndices = prefs.getString(KEY_CHECKED_INDICES, null)
?.split(SEPARATOR)
?.map(String::toInt) // or mapNotNull(String::toIntOrNull) to be super safe
?.forEach { checkedCalendarItems[it] = true }
// update the display - onBindViewHolder should be setting/clearing checkboxes
// by referring to the checked array
notifyDataSetChanged()
}
...
companion object {
// making these constants that both functions refer to avoids future bugs
// e.g. someone changing the separator in one function but not the other
const val SEPARATOR = ","
const val KEY_CHECKED_INDICES = "checked indices"
}
}
Then you can call these save/restore state functions on the adapter as appropriate, e.g. in onStop and onStart, passing in your SharedPreferences state object

Kotlin :Can't get the price of the item of RecyclerView on Fragment

I am attempting to make a recyclerView on Fragment ,
The idea is to get the price of the Item ,when the item been selected ,
and multiply by the select amount from the popup menu also.
Like below :
The recyclerView model item : item_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="120dp"
android:gravity="center"
android:id="#+id/cardView"
android:layout_margin="10dp"
android:background="#40E0D0"
android:layout_height="200dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Price:"
android:textColor="#color/black"
android:textSize="18sp"/>
<TextView
android:id="#+id/price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="100"
android:textColor="#color/black"
android:textSize="18sp"/>
</LinearLayout>
The model of the item : Product.kt :
package com.gearsrun.popmenuapplication
data class Product(var price : String)
private selectFuntion(itemPrice:Int){
}
ProductAdapter.kt
package com.gearsrun.popmenuapplication
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_layout.view.*
class ProductAdapter(private val productList:List<Product>,private val itemClick:(Int) -> Unit):RecyclerView.Adapter<ProductAdapter.ProductViewHolder>() {
private var selectedItemPosition :Int = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_layout,parent,false)
return ProductViewHolder(itemView,itemClick)
}
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
val currentItem = productList[position]
holder.price.text = currentItem.price.toString()
holder.itemView.setOnClickListener {
selectedItemPosition = position
notifyDataSetChanged()
}
if(selectedItemPosition == position){
holder.itemView.cardView.setBackgroundColor(Color.parseColor("#FAFAD2"))
}else{
holder.itemView.cardView.setBackgroundColor(Color.parseColor("#FFFFFF"))
}
}
override fun getItemCount() = productList.size
class ProductViewHolder(itemView: View, itemClick: (Int) -> Unit) :
RecyclerView.ViewHolder(itemView) {
val price : TextView = itemView.price
init {
itemView.setOnClickListener {
itemClick(price.text.toString().toInt()) //sortOf if you need String, change that on String in every declaration
}
}
}
}
Fragment.kt
package com.gearsrun.popmenuapplication.fragment
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import androidx.recyclerview.widget.LinearLayoutManager
import com.gearsrun.popmenuapplication.Product
import com.gearsrun.popmenuapplication.ProductAdapter
import com.gearsrun.popmenuapplication.R
import kotlinx.android.synthetic.main.fragment_home.*
class HomeFragment : Fragment(R.layout.fragment_home) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//init popup menu
val popupMenu = PopupMenu(
context,
selectedTv
)
//add menu items to popup menu
popupMenu.menu.add(Menu.NONE,0,0,"1")
popupMenu.menu.add(Menu.NONE,1,1,"2")
popupMenu.menu.add(Menu.NONE,2,2,"3")
popupMenu.menu.add(Menu.NONE,3,3,"4")
popupMenu.menu.add(Menu.NONE,4,4,"5")
//handle menu clicks
popupMenu.setOnMenuItemClickListener {menuItem ->
//get id of the item clicked
val id = menuItem.itemId
if(id==0){
selectedTv.text = "1"
}else if(id==1){
selectedTv.text = "2"
}else if(id==2){
selectedTv.text = "3"
}else if(id==3){
selectedTv.text = "4"
}else if(id==4){
selectedTv.text = "5"
}
true
}
//handle button click,show menu
selectedTv.setOnClickListener {
popupMenu.show()
}
//display recyclerview
val productList = generateProductList()
fun selectFuntion(itemPrice: Int){
Log.e("haha","You have click${itemPrice}")
}
var adapter = ProductAdapter(productList,::selectFuntion)
giftRecycleView.adapter = adapter
giftRecycleView.layoutManager = LinearLayoutManager(context,LinearLayoutManager.HORIZONTAL,false)
}
private fun generateProductList():List<Product> {
val list = ArrayList<Product>()
list.add(Product(1))
list.add(Product(2))
list.add(Product(3))
return list
}
}
Can anyone help me modify my code ?
I will need that the item's value can be catch once click ,and multiply by the select amount ,in order to get the total price .
Thank you so much in advance !!
ProductAdapter.kt
add this:
class ProductAdapter(
private val productList:List<Product>,
private val itemClick: (Int) -> Unit
): RecyclerView.Adapter<ProductAdapter.ProductViewHolder>() {
delete this:
interface onItemClickListener {
fun onItemClick(position: Int)
}
fun setOnItemClickListener(listener: onItemClickListener) {
mlistener = listener
}
replace this:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_layout,parent,false)
return ProductViewHolder(itemView,itemClick)
}
replace this:
class ProductViewHolder(itemView: View, itemClick: (Int) -> Unit) :
RecyclerView.ViewHolder(itemView) {
val price : TextView = itemView.price
init {
itemView.setOnClickListener {
itemclick(price.text.toInt()) //sortOf if you need String, change that on String in every declaration
}
}
}
replace in fragment:
var adapter = ProductAdapter(productList, ::yourFunction)
giftRecycleView.adapter = adapter
add in fragment or ViewModel:
private yourFunction(itemPrice: Int) {
// do something with price
}
if you need to edit that price just make return type:
(Int) -> Int sort of :D
and in adapter
price.text = itemclick(price.text.toInt()).toString()

Failed to take picture from arFragment - ARCore - Augmented Face

I follow code here but failed to capture my ArFragment. Please help me.
https://codelabs.developers.google.com/codelabs/sceneform-intro/index.html?index=..%2F..io2018#15
This project's link: https://github.com/DSparda/Breakable-Cam
Always get IOException: "Failed to save bitmap to disk"
My WritingArFragment:
class FaceArFragment : ArFragment() {
override fun getSessionConfiguration(session: Session?): Config {
return Config(session).apply { augmentedFaceMode = Config.AugmentedFaceMode.MESH3D }
}
override fun getSessionFeatures(): MutableSet<Session.Feature> {
return EnumSet.of(Session.Feature.FRONT_CAMERA)
}
override fun getAdditionalPermissions(): Array<String?>? {
val additionalPermissions = super.getAdditionalPermissions()
val permissionLength =
additionalPermissions?.size ?: 0
val permissions =
arrayOfNulls<String>(permissionLength + 1)
permissions[0] = Manifest.permission.WRITE_EXTERNAL_STORAGE
if (permissionLength > 0) {
System.arraycopy(
additionalPermissions,
0,
permissions,
1,
additionalPermissions!!.size
)
}
return permissions
}
/**
* Override to turn off planeDiscoveryController. Plane trackables are not supported with the
* front camera.
*/
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val layout = super.onCreateView(inflater, container, savedInstanceState) as FrameLayout
planeDiscoveryController.apply {
hide()
setInstructionView(null)
}
return layout
}
}
My Activity 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="takePictureViewModel"
type="com.example.breakablecam.screens.takingPicture.TakePictureViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/face_fragment_cointanier"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingBottom="16dp"
app:layout_constraintBottom_toTopOf="#+id/takePhotoView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<fragment
android:id="#+id/face_fragment"
android:name="com.example.breakablecam.screens.takingPicture.FaceArFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.fragment.app.FragmentContainerView>
<ImageView
android:id="#+id/backArrow"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="#+id/stickerView"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginBottom="16dp"
android:onClick="#{() -> takePictureViewModel.tapSticker()}"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/takePhotoView"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="#+id/stickerView1"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:visibility="gone"
android:onClick="#{() -> takePictureViewModel.tapSticker1()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="#+id/takePhotoView"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginBottom="16dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/makeupView"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/stickerView" />
<ImageView
android:id="#+id/makeupView"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginBottom="16dp"
android:onClick="#{() -> takePictureViewModel.tapMakeupView()}"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/takePhotoView" />
<ImageView
android:id="#+id/makeupView1a"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:onClick="#{() -> takePictureViewModel.tapMakeup1aView()}"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<ImageView
android:id="#+id/makeupView1"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginStart="16dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="#id/makeupView1a"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
My Activity:
class TakePictureActivity : AppCompatActivity() {
private lateinit var viewModel: TakePictureViewModel
private lateinit var arFragment: FaceArFragment
private var modelRenderable: ModelRenderable? = null
private var meshTexture: Texture? = null
private val faceNodeMap = HashMap<AugmentedFace, AugmentedFaceNode?>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityTakePictureBinding =
DataBindingUtil.setContentView(this, R.layout.activity_take_picture)
viewModel = ViewModelProvider(this).get(TakePictureViewModel::class.java)
binding.apply {
viewModel.apply {
setStickerViewSource(stickerView)
setMakeupViewSource(makeupView)
setTakePhotoViewSource(takePhotoView)
setMakeup1ViewSource(makeupView1)
setMakeup1aViewSource(makeupView1a)
setSticker1ViewSource(stickerView1)
setBackArrowSource(backArrow)
}
}
binding.takePictureViewModel = viewModel
binding.lifecycleOwner = this
viewModel.makeupTap.observe(this, Observer { check ->
if (check == 1) {
binding.apply {
makeupView.visibility = GONE
takePhotoView.visibility = GONE
stickerView.visibility = GONE
makeupView1.visibility = VISIBLE
makeupView1a.visibility = VISIBLE
backArrow.visibility = VISIBLE
val params =
faceFragmentCointanier.layoutParams as ConstraintLayout.LayoutParams
params.bottomToTop = R.id.makeupView1
}
viewModel.doneTapMakeup()
}
})
viewModel.stickerTap.observe(this, Observer { check ->
if (check == 1) {
binding.apply {
makeupView.visibility = GONE
takePhotoView.visibility = GONE
stickerView.visibility = GONE
stickerView1.visibility = VISIBLE
backArrow.visibility = VISIBLE
val params =
faceFragmentCointanier.layoutParams as ConstraintLayout.LayoutParams
params.bottomToTop = R.id.stickerView1
}
viewModel.doneTapSticker()
}
})
binding.apply {
backArrow.setOnClickListener {
makeupView.visibility = VISIBLE
takePhotoView.visibility = VISIBLE
stickerView.visibility = VISIBLE
makeupView1.visibility = GONE
makeupView1a.visibility = GONE
backArrow.visibility = GONE
stickerView1.visibility = GONE
backArrow.visibility = GONE
val params =
faceFragmentCointanier.layoutParams as ConstraintLayout.LayoutParams
params.bottomToTop = R.id.takePhotoView
}
}
arFragment = supportFragmentManager.findFragmentById(R.id.face_fragment) as FaceArFragment
val sceneView = arFragment.arSceneView
sceneView.cameraStreamRenderPriority = Renderable.RENDER_PRIORITY_FIRST
val scene = sceneView.scene
loadTexture(R.drawable.makeup1a)
loadModel(R.raw.fox_face)
viewModel.makeupTap1a.observe(this, Observer { check ->
when (check) {
1 -> {
scene.addOnUpdateListener {
val collection: Collection<AugmentedFace>? =
sceneView.session?.getAllTrackables(AugmentedFace::class.java)
collection?.forEach { face ->
if (!faceNodeMap.containsKey(face)) {
val faceNode = AugmentedFaceNode(face)
faceNode.apply {
setParent(scene)
faceMeshTexture = meshTexture
}
faceNodeMap[face] = faceNode
}
}
val iterator = faceNodeMap.entries.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
val face = entry.key
if (face.trackingState == TrackingState.STOPPED) {
val faceNode = entry.value
faceNode!!.setParent(null)
iterator.remove()
}
}
}
}
2 -> {
val children: List<Node> =
ArrayList(arFragment.arSceneView.scene.children)
for (node in children) {
if (node is AnchorNode) {
if (node.anchor != null) {
node.anchor?.detach()
}
}
if (node !is Camera && node !is Sun) {
node.setParent(null)
}
}
}
}
})
viewModel.sticker1Tap.observe(this, Observer { check ->
when (check) {
1 -> {
scene.addOnUpdateListener {
val collection: Collection<AugmentedFace>? =
sceneView.session?.getAllTrackables(AugmentedFace::class.java)
collection?.forEach { face ->
if (!faceNodeMap.containsKey(face)) {
val faceNode = AugmentedFaceNode(face)
faceNode.apply {
setParent(scene)
faceRegionsRenderable = modelRenderable
}
faceNodeMap[face] = faceNode
}
}
val iterator = faceNodeMap.entries.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
val face = entry.key
if (face.trackingState == TrackingState.STOPPED) {
val faceNode = entry.value
faceNode!!.setParent(null)
iterator.remove()
}
}
}
}
2 -> {
val children: List<Node> =
ArrayList(arFragment.arSceneView.scene.children)
for (node in children) {
if (node is AnchorNode) {
if (node.anchor != null) {
node.anchor?.detach()
}
}
if (node !is Camera && node !is Sun) {
node.setParent(null)
}
}
}
}
})
binding.takePhotoView.setOnClickListener {
takePhoto()
}
}
private fun loadTexture(tex: Int) {
Texture.builder()
.setSource(this, tex)
.build()
.thenAccept { texture -> meshTexture = texture }
}
private fun loadModel(mod: Int) {
ModelRenderable.builder()
.setSource(this, mod)
.build()
.thenAccept { model ->
model.apply {
isShadowCaster = false // optional
isShadowReceiver = false
}
modelRenderable = model
}
}
private fun generateFilename(): String? {
val date =
SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
.format(Date())
return Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES
).toString() + File.separator + "Sceneform/" + date + "_screenshot.jpg"
}
#Throws(IOException::class)
private fun saveBitmapToDisk(bitmap: Bitmap, filename: String) {
val out = File(filename)
if (!out.parentFile.exists()) {
out.parentFile.mkdirs()
}
try {
FileOutputStream(filename).use { outputStream ->
ByteArrayOutputStream().use { outputData ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputData)
outputData.writeTo(outputStream)
outputStream.flush()
outputStream.close()
}
}
} catch (ex: IOException) {
throw IOException("Failed to save bitmap to disk", ex)
}
}
private fun takePhoto() {
val filename = generateFilename()
val view: ArSceneView = arFragment.getArSceneView()
// Create a bitmap the size of the scene view.
val bitmap = Bitmap.createBitmap(
view.width, view.height,
Bitmap.Config.ARGB_8888
)
// Create a handler thread to offload the processing of the image.
val handlerThread = HandlerThread("PixelCopier")
handlerThread.start()
// Make the request to copy.
PixelCopy.request(view, bitmap, { copyResult ->
if (copyResult === PixelCopy.SUCCESS) {
try {
saveBitmapToDisk(bitmap, filename!!)
} catch (e: IOException) {
val toast = Toast.makeText(
this, e.toString(),
Toast.LENGTH_LONG
)
toast.show()
return#request
}
val snackbar = Snackbar.make(
findViewById(android.R.id.content),
"Photo saved", Snackbar.LENGTH_LONG
)
snackbar.setAction(
"Open in Photos"
) { v: View? ->
val photoFile = File(filename)
val photoURI = FileProvider.getUriForFile(
this,
this.getPackageName()
.toString() + ".ar.codelab.name.provider",
photoFile
)
val intent = Intent(Intent.ACTION_VIEW, photoURI)
intent.setDataAndType(photoURI, "image/*")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
}
snackbar.show()
} else {
val toast = Toast.makeText(
this,
"Failed to copyPixels: $copyResult", Toast.LENGTH_LONG
)
toast.show()
}
handlerThread.quitSafely()
}, Handler(handlerThread.looper))
}
}
Thank everyone so much.
try add
<application android:requestLegacyExternalStorage="true" ... > ... </application>
to your manifest.
I assume that this is related to the upcoming scoped storage enforcement in Android 11.
You have to add the tags in your AndroidManifest.xml specified in the codelabs(permissions and file provider.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.ar.codelab.name.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths"/>
</provider>
Then you have to migrate from using File to using Uris and ContentResolvers because Android 10 deprecated the File functionality and you have to use MediaStore.
https://developer.android.com/training/data-storage/shared/media

Kotlin RecyclerView how select first list item after activity load? tried (code below) failed :(

I can select 1st item in RecyclerView (working code below, click on "email" FAB button - boom! 1st selected)
However I cannot get 1st RecyclerView click in code when app starts,
I looked for override fun onViewCreated() but nothing like it for activity,
where can I call selectFirstOnList() after activity & recyclerview fully rendered?
what event fires on activity fully rendered/loaded?
or is my noob kotlin way of thinking flawed? its 99.9% working :(
Thanks in advance for any help :)
SOLUTIUON (code edited to bottom of this post will replace code in ItemListActivity.kt)
postdelay select first (you can see from original code I'd already tried something like this!) PHEW! hope this helps someone :)
ItemListActivity.kt
package ie.dpsystems.asm.list
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import ie.dpsystems.asm.R
import ie.dpsystems.asm.data.Orientation
import ie.dpsystems.asm.data.State
import ie.dpsystems.asm.data.State.Companion.trackLog
import ie.dpsystems.asm.detail.ItemDetailActivity
import ie.dpsystems.asm.detail.ItemDetailFragment
import kotlinx.android.synthetic.main.activity_item_list.*
import kotlinx.android.synthetic.main.item_list_content.view.*
import kotlinx.android.synthetic.main.item_list.*
import android.os.Handler
class ItemListActivity : AppCompatActivity() {
private var twoPane: Boolean = false
private var showToastEvents: Boolean = true
private fun uiIsTwoPane():Boolean{
try{
if (item_detail_container != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-w900dp).
// If this view is present, then the
// activity should be in two-pane mode.
return true
}
}catch(e:Exception)
{
Toast.makeText(this,"E: ${e.toString()}",Toast.LENGTH_SHORT).show()
}
return false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
trackLog(this,"onCreate()")
setContentView(R.layout.activity_item_list)
twoPane = uiIsTwoPane()
setSupportActionBar(toolbar)
toolbar.title = title
fab.setOnClickListener { view -> onListFabClick(view) }
setupRecyclerView(recycleview_list)
refreshUI()
}
private fun setupRecyclerView(recyclerView: RecyclerView) {
trackLog(this,"setupRecyclerView()")
State.dataRows = ListContent.ITEMS
recyclerView.adapter = SimpleItemRecyclerViewAdapter(
this,
twoPane,
this
)
var recycleViewUI = recycleview_list
postAndNotifyAdapter(Handler(), recycleViewUI)
}
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
trackLog(this,"onConfigurationChanged()")
}
override fun onStart() {
super.onStart()
trackLog(this,"onStart()")
}
override fun onResume() {
super.onResume()
trackLog(this,"onResume() A")
checkOrientationChange()
refreshUI()
trackLog(this,"onResume() B")
}
private fun checkOrientationChange() {
trackLog(this,"checkOrientationChange()")
if (State.lastOrientation != null) {
val thisOrientation = if (twoPane) Orientation.landscape else Orientation.portrate
if (thisOrientation != State.lastOrientation) {
putDetailFragmentOnDetailFragmentHolder()
}
}
}
private fun putDetailFragmentOnDetailFragmentHolder() {
trackLog(this,"putDetailFragmentOnDetailFragmentHolder()")
if(item_detail_container!=null){
val fragment = ItemDetailFragment() //val fragment = ItemDetailFragment().apply {arguments = Bundle().apply {putInt("SOME UNIQUE TAG", selectedItemUniqueID)}}
val container = item_detail_container
container.removeAllViewsInLayout()
supportFragmentManager.beginTransaction().replace(R.id.item_detail_container, fragment).commit()
}
}
override fun onPause() {
super.onPause()
trackLog(this,"onPause()")
}
override fun onStop() {
super.onStop()
trackLog(this,"onStop()")
}
class SimpleItemRecyclerViewAdapter( private val parentActivity: ItemListActivity
,private val twoPane: Boolean
,private val context: Context //private val context = parentActivity.applicationContext
) : RecyclerView.Adapter<SimpleItemRecyclerViewAdapter.ViewHolder>() {
override fun getItemCount(): Int {
return State.dataRows.size
}
override fun onBindViewHolder(recyclerViewRow: ViewHolder, position: Int) {
trackLog(context, "onBindViewHolder()")
val dataThisRow = State.dataRows[position]
dataThisRow.listRowIndex = position
setDataToRecyclerRow(recyclerViewRow, dataThisRow)
recyclerViewRow.itemView.setOnClickListener {
onListItemClick(dataThisRow.uniqueID)
}
if (dataThisRow.uniqueID == State.selectedListItemUniqueId) {
recyclerViewRow.idRow.setBackgroundColor(Color.parseColor("#009688"))
} else {
recyclerViewRow.idRow.setBackgroundColor(Color.WHITE)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
trackLog(context, "onCreateViewHolder()")
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_list_content, parent, false)
return ViewHolder(view, 1)
}
override fun onViewAttachedToWindow(holder: ViewHolder) {
super.onViewAttachedToWindow(holder)
trackLog(context, "onViewAttachedToWindow()")
}
inner class ViewHolder(itemView: View, position: Int) : RecyclerView.ViewHolder(itemView) {
val idView: TextView = itemView.id_text
val contentView: TextView = itemView.content
val idRow = itemView.id_row_linear_layout
}
private fun setDataToRecyclerRow(recyclerViewRow: ViewHolder, data: ListContent.ListItem) {
trackLog(context, "setDataToRecyclerRow(id: ${data.id})")
recyclerViewRow.idView.text = data.id
recyclerViewRow.contentView.text = data.itemTitle
recyclerViewRow.itemView.tag = data
}
private fun onListItemClick(selectedItemUniqueID: Int) {
trackLog(context, "onListItemClick($selectedItemUniqueID)")
State.selectedListItemUniqueId = selectedItemUniqueID
if (twoPane) {
State.lastOrientation = Orientation.landscape
putDetailFragmentOnDetailActivity()
} else {
State.lastOrientation = Orientation.portrate
launchDetailActivity()
}
notifyDataSetChanged()
}
private fun launchDetailActivity() {
trackLog(context, "launchDetailActivity()")
val intent = Intent(
context,
ItemDetailActivity::class.java
) //val intent = Intent(context, ItemDetailActivity::class.java).apply {putExtra(ItemDetailFragment.ARG_ITEM_ID, selectedItemUniqueID)}
context.startActivity(intent)
}
private fun putDetailFragmentOnDetailActivity() {
trackLog(context, "putDetailFragmentOnDetailFragmentHolder()")
val fragment =
ItemDetailFragment() //val fragment = ItemDetailFragment().apply {arguments = Bundle().apply {putInt("SOME UNIQUE TAG", selectedItemUniqueID)}}
val container = parentActivity.item_detail_container
container.removeAllViewsInLayout()
parentActivity.supportFragmentManager.beginTransaction()
.replace(R.id.item_detail_container, fragment).commit()
}
}
private fun onListFabClick(view: View) {
trackLog(this, "onListFabClick()")
selectFirstOnList()
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
private fun refreshUI() {
trackLog(this, "refreshUI()")
var recycleViewUI = recycleview_list
if (State.selectedListItemUniqueId == null) {
selectFirstOnList()
} else {
val selectedListItem: ListContent.ListItem? =
State.findDataRowByUniqueId(State.selectedListItemUniqueId)
if (selectedListItem == null) {
selectFirstOnList()
recycleViewUI.findViewHolderForAdapterPosition(0)?.itemView?.performClick()
} else {
recycleViewUI.getLayoutManager()
?.scrollToPosition(selectedListItem.listRowIndex)
}
}
}
private fun selectFirstOnList() {
trackLog(this, "selectFirstOnList()")
if (twoPane) {
var recycleViewUI = recycleview_list
//recycleViewUI.getLayoutManager()?.scrollToPosition(0)
recycleViewUI.findViewHolderForAdapterPosition(0)?.itemView?.performClick()
}
}
protected fun postAndNotifyAdapter(handler: Handler, recyclerView: RecyclerView) {
trackLog(this, "postAndNotifyAdapter()")
/*
handler.post(Runnable {
if (!recyclerView.isComputingLayout) {
// This will call first item by calling "performClick()" of view.
(recyclerView.findViewHolderForLayoutPosition(0) as RecyclerView.ViewHolder).itemView.performClick()
} else {
postAndNotifyAdapter(handler, recyclerView) //, adapter
}
})
*/
}
}
class State (hold selected on screen rotation/activity changes)
import android.content.Context
import android.util.Log
import android.widget.Toast
import ie.dpsystems.asm.list.ListContent
import java.util.ArrayList
enum class Orientation { portrate, landscape }
class State {
companion object {
public var selectedListItemUniqueId:Int? = null
public var dataRows: MutableList<ListContent.ListItem> = ArrayList()
public var lastOrientation:Orientation? = null
public fun findDataRowByUniqueId(uniqueID:Int?):ListContent.ListItem?{
if(uniqueID==null) return null
return State.dataRows.find { it.uniqueID == uniqueID}
}
public fun trackLog(context: Context, text:String){
//Toast.makeText(context,text, Toast.LENGTH_LONG).show()
Log.d("track",text)
}
}
}
src/main/res/layout-w900dp/item_list.xml (2 pane for tablet)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:baselineAligned="false"
android:divider="?android:attr/dividerHorizontal"
android:orientation="horizontal"
android:showDividers="middle"
tools:context=".list.ItemListActivity">
<!--
This layout is a two-pane layout for list / detail
-->
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/recycleview_list"
android:name="ie.dpsystems.asm.ItemListFragment"
android:layout_width="#dimen/item_width"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context="ie.dpsystems.asm.list.ItemListActivity"
tools:listitem="#layout/item_list_content" />
<FrameLayout
android:id="#+id/item_detail_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3" />
</LinearLayout>
item_list_content.xml (RecyclerView per row layout)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/id_row_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="#+id/id_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem" />
<TextView
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="#dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem" />
</LinearLayout>
object ListContent (to put fake data into list during development)
import java.util.ArrayList
import java.util.HashMap
/**
* Helper class for providing sample itemTitle for user interfaces created by
* Android template wizards.
*
* TODO: Replace all uses of this class before publishing your app.
*/
object ListContent {
/**
* An array of sample (dummy) items.
*/
val ITEMS: MutableList<ListItem> = ArrayList()
/**
* A map of sample (dummy) items, by ID.
*/
val ITEM_MAP: MutableMap<String, ListItem> = HashMap()
private val COUNT = 25
init {
// Add some sample items.
for (i in 1..COUNT) {
addItem(fakeGetRecordFromSqlite(i))
}
}
private fun addItem(item: ListItem) {
ITEMS.add(item)
ITEM_MAP.put(item.id, item)
}
private fun fakeGetRecordFromSqlite(position: Int): ListItem {
return ListItem(position, -1, position.toString(), "Item " + position, fakeGetRecordCollectionFromSqlite(position))
}
private fun fakeGetRecordCollectionFromSqlite(position: Int): String {
val builder = StringBuilder()
builder.append("Details about Item: ").append(position)
for (i in 0..position - 1) {
builder.append("\nMore details information here.")
}
return builder.toString()
}
/**
* A dummy item representing a piece of itemTitle.
*/
data class ListItem(val uniqueID:Int, var listRowIndex:Int, val id: String, val itemTitle: String, val details: String) {
override fun toString(): String = itemTitle
}
}
class ItemDetailFragment (display details in detail fragment)
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import ie.dpsystems.asm.R
import ie.dpsystems.asm.data.State
import ie.dpsystems.asm.list.ListContent
import kotlinx.android.synthetic.main.activity_item_detail.*
import kotlinx.android.synthetic.main.item_detail.view.*
/**
* A fragment representing a single Item detail screen.
* This fragment is either contained in a [ItemListActivity]
* in two-pane mode (on tablets) or a [ItemDetailActivity]
* on handsets.
*/
class ItemDetailFragment : Fragment() {
/**
* The dummy itemTitle this fragment is presenting.
*/
private var item: ListContent.ListItem? = null
private var selectedItemUniqueID:Int? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//arguments?.let { if (it.containsKey(ARG_ITEM_ID)) {val uniqueID = it.getInt(ARG_ITEM_ID)}}
item = State.findDataRowByUniqueId(State.selectedListItemUniqueId)
activity?.toolbar_layout?.title = item?.itemTitle
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.item_detail, container, false)
item?.let {rootView.item_detail.text = it.details}
return rootView
}
}
class ItemDetailActivity (for single pane screen size devices)
import android.content.Intent
import android.os.Bundle
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NavUtils
import android.view.MenuItem
import android.view.View
import ie.dpsystems.asm.list.ItemListActivity
import ie.dpsystems.asm.R
import kotlinx.android.synthetic.main.activity_item_detail.*
import kotlinx.android.synthetic.main.activity_item_detail.fab
/**
* An activity representing a single Item detail screen. This
* activity is only used on narrow width devices. On tablet-size devices,
* item details are presented side-by-side with a list of items
* in a [ItemListActivity].
*/
class ItemDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_item_detail)
setSupportActionBar(detail_toolbar)
fab.setOnClickListener { view -> onListFabClick(view) }
supportActionBar?.setDisplayHomeAsUpEnabled(true) // Show the Up button in the action bar.
putDetailFragmentOnDetailActivity(savedInstanceState)
}
private fun putDetailFragmentOnDetailActivity(savedInstanceState: Bundle?){ // Create the detail fragment and add it to the activity using a fragment transaction.
// savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity
// (e.g. when rotating the screen from portrait to landscape).
// In this case, the fragment will automatically be re-added
// to its container so we don't need to manually add it.
// For more information, see the Fragments API guide at:
// http://developer.android.com/guide/components/fragments.html
if (savedInstanceState == null) {
val fragment = ItemDetailFragment() //val fragment = ItemDetailFragment().apply {arguments = Bundle().apply {putString("SOME UNIQUE TAG",intent.getStringExtra("SOME UNIQUE TAG"))}}
supportFragmentManager.beginTransaction().add(R.id.item_detail_container, fragment).commit()
}
}
private fun onListFabClick(view: View?) {
if(view!=null) Snackbar.make(view, "Replace with your own detail action", Snackbar.LENGTH_LONG).setAction("Action", null).show()
}
override fun onOptionsItemSelected(item: MenuItem) =
when (item.itemId) {
android.R.id.home -> {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
NavUtils.navigateUpTo(this, Intent(this, ItemListActivity::class.java))
true
}
else -> super.onOptionsItemSelected(item)
}
}
SOLUTIUON
postdelay select first (you can see from original code I'd already tried something like this!) PHEW! hope this helps someone :)
protected fun postAndNotifyAdapter(handler: Handler, recyclerView: RecyclerView) {
trackLog(this, "postAndNotifyAdapter() ${State.selectedListItemUniqueId}")
if (twoPane && State.selectedListItemUniqueId==null) {
Handler().postDelayed({
if (!recyclerView.isComputingLayout) {
trackLog(this, "postAndNotifyAdapter() !recyclerView.isComputingLayout ${State.selectedListItemUniqueId}")
selectFirstOnList()
} else {
postAndNotifyAdapter(handler, recyclerView) //, adapter
}
}, 1000)
}
}
I've edited my original post above to be complete with answer!
I recommend you use an interface to notify when recyclerview is done rendering it's items.
How to know when the RecyclerView has finished laying down the items?

Kotlin read and display data from Google Sheets

Can somebody help me accomplish what I am trying to. I have spent hours watching videos and surfing Google to do it myself. But, I couldn't as there's no help I could find to read just one cell value from Google Sheets and I couldn't also find code for Kotlin.
All what I want is to display the value in cell B2 in my Google sheets in a textview on MainActivity.
I am new to Android app development.
Thanks in advance.
i have code for to get the value from google sheets let me share it to you.
First of all you need to create one spreadsheet and you need to create your api key to get access to spreadsheet api.
MY SpreadSheet data
--> When you create spread sheet in file options -> publish to web. and it will generate a link.
-> get the sheets id from that link. it will be like
https://docs.google.com/spreadsheets/d/your-id-//edit#gid=0
--> On given url you have to put your sheets id sheet name and the api key that you have generated from google console
From generated url you can get the json format of your data into spreadsheet.
Your url will look like this.
https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/YOUR_SHEET_NAME?alt=json&key=YOUR_API_KEY
Open this url into browser and get the json format of your data from spreadsheet.
After getting the data
Here is the code to retrive the data from spreadsheet.
first you need a model class to get the data from json.
class model {
// variables for our first name,
// last name, email and avatar
private var first_name: String? = null
private var last_name: String? = null
private var email: String? = null
constructor(first_name: String, last_name: String, email: String){
this.first_name = first_name;
this.last_name = last_name;
this.email = email;
}
fun getFirst_name(): String? {
return first_name
}
fun setFirst_name(first_name: String?) {
this.first_name = first_name
}
fun getLast_name(): String? {
return last_name
}
fun setLast_name(last_name: String?) {
this.last_name = last_name
}
fun getEmail(): String? {
return email
}
fun setEmail(email: String?) {
this.email = email
}
}
BaseModel.kt
class BaseModel {
#SerializedName("range")
#Expose
private var range: String? = null
#SerializedName("majorDimension")
#Expose
private var majorDimension: String? = null
#SerializedName("values")
#Expose
private var values: List<List<model?>?>? = null
fun getRange(): String? {
return range
}
fun setRange(range: String?) {
this.range = range
}
fun getMajorDimension(): String? {
return majorDimension
}
fun setMajorDimension(majorDimension: String?) {
this.majorDimension = majorDimension
}
fun getValues(): List<List<model?>?>? {
return values
}
fun setValues(values: List<List<model?>?>?) {
this.values = values
}
}
activity_main.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"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/idRVUsers"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="#layout/user_rv_item" />
<!--we are adding progress bar for thepurpose of loading-->
<ProgressBar
android:id="#+id/idPBLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
user_rv_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
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"
android:elevation="8dp"
app:cardCornerRadius="8dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp">
<!--image view for displaying user image-->
<ImageView
android:id="#+id/idIVUser"
android:layout_width="100dp"
android:src="#drawable/ic_launcher_foreground"
android:layout_height="100dp"
android:layout_margin="10dp" />
<!--text view for displaying first name-->
<TextView
android:id="#+id/idTVFirstName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_toEndOf="#id/idIVUser"
android:layout_toRightOf="#id/idIVUser"
android:text="First Name"
android:textColor="#color/black"
android:textSize="15sp" />
<!--text view for displaying last name-->
<TextView
android:id="#+id/idTVLastName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/idTVFirstName"
android:layout_marginTop="10dp"
android:layout_toEndOf="#id/idIVUser"
android:layout_toRightOf="#id/idIVUser"
android:text="Last Name"
android:textColor="#color/black"
android:textSize="15sp" />
<!--text view for displaying user email-->
<TextView
android:id="#+id/idTVEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/idTVLastName"
android:layout_marginTop="10dp"
android:layout_toEndOf="#id/idIVUser"
android:layout_toRightOf="#id/idIVUser"
android:text="Email"
android:textColor="#color/black"
android:textSize="15sp" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
rv_adapter.kt
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class UserRVAdapter(modelArraylist: ArrayList<model>) :
RecyclerView.Adapter<UserRVAdapter.ViewHolder>() {
// variable for our array list and context.
private val userModalArrayList: ArrayList<model> = modelArraylist
private val context: Context? = null
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val firstNameTV: TextView? = itemView.findViewById(R.id.idTVFirstName)
var lastNameTV: TextView? = itemView.findViewById(R.id.idTVLastName)
var emailTV: TextView? = itemView.findViewById(R.id.idTVEmail)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view: View =
LayoutInflater.from(parent.context).inflate(R.layout.user_rv_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// getting data from our array list in our modal class.
// getting data from our array list in our modal class.
val userModal: model = userModalArrayList[position]
// on the below line we are setting data to our text view.
// on the below line we are setting data to our text view.
holder.firstNameTV?.setText(userModal.getFirst_name())
holder.lastNameTV?.setText(userModal.getLast_name())
holder.emailTV?.setText(userModal.getEmail())
}
override fun getItemCount(): Int {
return userModalArrayList.size
}
}
MainActivity.kt
import android.os.Bundle
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.volley.Request
import com.android.volley.Response
import com.android.volley.VolleyError
import com.android.volley.toolbox.JsonObjectRequest
import com.android.volley.toolbox.Volley
import org.json.JSONException
import org.json.JSONObject
class MainActivity : AppCompatActivity() {
private var userModalArrayList: ArrayList<model>? = null
private var userRVAdapter: UserRVAdapter? = null
private var userRV: RecyclerView? = null
private var loadingPB: ProgressBar? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
userModalArrayList = ArrayList()
userRV = findViewById(R.id.idRVUsers)
loadingPB = findViewById(R.id.idPBLoading)
getDataFromAPI()
}
private fun getDataFromAPI() {
val url=
"https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/YOUR_SHEET_NAME?alt=json&key=YOUR_API_KEY"
val queue = Volley.newRequestQueue(this#MainActivity)
val jsonObjectRequest =
JsonObjectRequest(
Request.Method.GET,
url,
null,
object : Response.Listener<JSONObject> {
override fun onResponse(response: JSONObject) {
loadingPB!!.visibility = View.GONE
try {
// val feedObj = response.getJSONObject("")
val entryArray = response.getJSONArray("values")
for (i in 1 until entryArray.length()) {
val entryObj = entryArray.getJSONArray(i)
val firstName =
entryObj[2].toString()
val lastName = entryObj[3].toString()
// entryObj.getJSONObject("gsx\$lastname").getString("\$t")
val email = entryObj[1].toString()
userModalArrayList!!.add(model(firstName, lastName, email))
// passing array list to our adapter class.
userRVAdapter = UserRVAdapter(userModalArrayList!!)
// setting layout manager to our recycler view.
userRV!!.layoutManager = LinearLayoutManager(this#MainActivity)
// setting adapter to our recycler view.
userRV!!.adapter = userRVAdapter
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
},
object : Response.ErrorListener {
override fun onErrorResponse(error: VolleyError?) {
// handline on error listener method.
Toast.makeText(this#MainActivity, "Fail to get data..", Toast.LENGTH_SHORT)
.show()
}
})
queue.add(jsonObjectRequest);
}
}
You can use this code to get data from your whole google spread sheet.
I suggest making google sheet public, then you can access it for example as csv or in other formats, but csv is simplest. You can construct url based on sheet id:
val url = "https://docs.google.com/spreadsheets/d/e/${id}/pub?output=csv"
You can get contents for example using ktor:
suspend fun getUrlAsString(url: String): String {
val client = HttpClient(Android) {
}
return client.get<String>(url)
}
Example on how to parse csv, but you can use some library or parse text yourself, it's pretty simple:
fun parseCsv(text: String): List<Map<String, String>> {
val reader = CSVReaderBuilder(StringReader(text))
.build()
val lines = reader.readAll()
val firstLine = lines.removeAt(0)
val result = lines.map { line ->
firstLine.mapIndexed { index, label ->
label to line[index]
}.toMap()
}
if (result.size > 4)
println(result[4])
return result
}