RxJava Networking cannot be instantiated - java

I am trying to run an application that I rewrote in Kotlin from the following link:
https://www.androidhive.info/RxJava/android-rxjava-networking-with-retrofit-gson-notes-app/
The example was originally code base was coded in Java. I am getting the following error upon running the application.
2018-11-16 12:12:38.173 11843-11843/com.touchsides.rxjavanetworking E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.touchsides.rxjavanetworking, PID: 11843
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.touchsides.rxjavanetworking/com.touchsides.rxjavanetworking.view.MainActivity}: java.lang.InstantiationException: java.lang.Class<com.touchsides.rxjavanetworking.view.MainActivity> cannot be instantiated
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2843)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: java.lang.InstantiationException: java.lang.Class<com.touchsides.rxjavanetworking.view.MainActivity> cannot be instantiated
at java.lang.Class.newInstance(Native Method)
at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:69)
at android.support.v4.app.CoreComponentFactory.instantiateActivity(CoreComponentFactory.java:43)
at android.app.Instrumentation.newActivity(Instrumentation.java:1215)
The MainActivity code is as follows:
abstract class MainActivity : AppCompatActivity()
{
lateinit var apiService: ApiService
var disposable = CompositeDisposable()
lateinit var mAdapter: NotesAdapter
var noteList = ArrayList<Note>()
companion object
{
val TAG = MainActivity::class.java.simpleName;
}
#BindView(R.id.coordinator_layout) var coordinatorLayout: CoordinatorLayout? = null
#BindView(R.id.recycler_view) var recyclerView: RecyclerView? = null
#BindView(R.id.txt_empty_notes_view) var noNotesView: TextView? = null
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.setTitle(getString(R.string.activity_title_home))
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
showNoteDialog(false, null, -1);
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean
{
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean
{
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
return when (item.itemId)
{
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
}
/**
* Registering new user
* sending unique id as device identification
* https://developer.android.com/training/articles/user-data-ids.html
*/
private fun registerUser()
{
// unique id to identify the device
val uniqueId = UUID.randomUUID().toString()
disposable.add(apiService.register(uniqueId).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeWith(
object : DisposableSingleObserver<User>()
{
override fun onSuccess(user: User)
{
// Storing user API Key in preferences
user.apiKey?.let { PrefUtils.storeApiKey(applicationContext, it) }
Toast.makeText(applicationContext,
"Device is registered successfully! ApiKey: " + PrefUtils.getApiKey(applicationContext),
Toast.LENGTH_LONG).show()
}
override fun onError(e: Throwable)
{
Log.e(TAG, "onError: " + e.message)
showError(e)
}
}))
}
/**
* Creating new note
*/
private fun createNote(note: String)
{
disposable.add(apiService.createNote(note).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeWith(
object : DisposableSingleObserver<Note>()
{
override fun onSuccess(note: Note)
{
if (!TextUtils.isEmpty(note.error))
{
Toast.makeText(applicationContext, note.error, Toast.LENGTH_LONG).show()
return
}
Log.d(TAG, "new note created: " + note.id + ", " + note.note + ", " + note.timestamp)
// Add new item and notify adapter
noteList.add(0, note)
mAdapter.notifyItemInserted(0)
toggleEmptyNotes()
}
override fun onError(e: Throwable)
{
Log.e(TAG, "onError: " + e.message)
showError(e)
}
}))
}
/**
* Updating a note
*/
private fun updateNote(noteId: Int, note: String, position: Int)
{
disposable.add(apiService.updateNote(noteId,
note).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeWith(object :
DisposableCompletableObserver()
{
override fun onComplete()
{
Log.d(TAG, "Note updated!")
val n = noteList.get(position)
n.note = (note)
// Update item and notify adapter
noteList.set(position, n)
mAdapter.notifyItemChanged(position)
}
override fun onError(e: Throwable)
{
Log.e(TAG, "onError: " + e.message)
showError(e)
}
}))
}
/**
* Deleting a note
*/
private fun deleteNote(noteId: Int, position: Int)
{
Log.e(TAG, "deleteNote: $noteId, $position")
disposable.add(apiService.deleteNote(noteId).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeWith(
object : DisposableCompletableObserver()
{
override fun onComplete()
{
Log.d(TAG, "Note deleted! $noteId")
// Remove and notify adapter about item deletion
noteList.removeAt(position)
mAdapter.notifyItemRemoved(position)
Toast.makeText(this#MainActivity, "Note deleted!", Toast.LENGTH_SHORT).show()
toggleEmptyNotes()
}
override fun onError(e: Throwable)
{
Log.e(TAG, "onError: " + e.message)
showError(e)
}
}))
}
/**
* Shows alert dialog with EditText options to enter / edit
* a note.
* when shouldUpdate=true, it automatically displays old note and changes the
* button text to UPDATE
*/
private fun showNoteDialog(shouldUpdate: Boolean, note: Note?, position: Int)
{
val layoutInflaterAndroid = LayoutInflater.from(applicationContext)
val view = layoutInflaterAndroid.inflate(R.layout.note_dialog, null)
val alertDialogBuilderUserInput = AlertDialog.Builder(this#MainActivity)
alertDialogBuilderUserInput.setView(view)
val inputNote = view.findViewById<EditText>(R.id.note)
val dialogTitle = view.findViewById<TextView>(R.id.dialog_title)
dialogTitle.setText(if (!shouldUpdate) getString(R.string.lbl_new_note_title) else getString(R.string.lbl_edit_note_title))
if (shouldUpdate && note != null)
{
inputNote.setText(note.note)
}
alertDialogBuilderUserInput.setCancelable(false).setPositiveButton(if (shouldUpdate) "update" else "save",
DialogInterface.OnClickListener { dialogBox, id -> })
.setNegativeButton("cancel", DialogInterface.OnClickListener { dialogBox, id -> dialogBox.cancel() })
val alertDialog = alertDialogBuilderUserInput.create()
alertDialog.show()
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(View.OnClickListener {
// Show toast message when no text is entered
if (TextUtils.isEmpty(inputNote.text.toString()))
{
Toast.makeText(this#MainActivity, "Enter note!", Toast.LENGTH_SHORT).show()
return#OnClickListener
} else
{
alertDialog.dismiss()
}
// check if user updating note
if (shouldUpdate && note != null)
{
// update note by it's id
updateNote(note.id, inputNote.text.toString(), position)
} else
{
// create new note
createNote(inputNote.text.toString())
}
})
}
/**
* Opens dialog with Edit - Delete options
* Edit - 0
* Delete - 0
*/
private fun showActionsDialog(position: Int)
{
val colors = arrayOf<CharSequence>("Edit", "Delete")
val builder = AlertDialog.Builder(this)
builder.setTitle("Choose option")
builder.setItems(colors) { dialog, which ->
if (which == 0)
{
showNoteDialog(true, noteList.get(position), position)
} else
{
deleteNote(noteList.get(position).id, position)
}
}
builder.show()
}
private fun toggleEmptyNotes()
{
if (noteList.size > 0)
{
noNotesView?.setVisibility(View.GONE)
} else
{
noNotesView?.setVisibility(View.VISIBLE)
}
}
/**
* Showing a Snackbar with error message
* The error body will be in json format
* {"error": "Error message!"}
*/
fun showError(e: Throwable)
{
var message = ""
try
{
if (e is IOException)
{
message = "No internet connection!"
}
else (e is HttpException)
{
var error = e as HttpException
var errorBody = error.response().errorBody().toString()
val jObj = JSONObject(errorBody)
message = jObj.getString("error")
}
}
catch (e1: IOException)
{
e1.printStackTrace()
}
catch (e1: JSONException)
{
e1.printStackTrace()
}
catch (e1: Exception)
{
e1.printStackTrace()
}
if (TextUtils.isEmpty(message))
{
message = "Unknown error occurred! Check LogCat.";
}
val snackbar = coordinatorLayout?.let { Snackbar.make(it, message, Snackbar.LENGTH_LONG) }
val sbView = snackbar?.getView()
val textView = sbView?.findViewById<TextView>(android.support.design.R.id.snackbar_text)
textView?.setTextColor(Color.YELLOW)
snackbar?.show()
}
fun whiteNotificationBar(view: View)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
var flags = view.getSystemUiVisibility();
flags = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
view.setSystemUiVisibility(flags);
getWindow().setStatusBarColor(Color.WHITE);
}
}
override fun onDestroy()
{
super.onDestroy()
disposable.dispose()
}
}

I found out what the issue was. It seems by using the code complete option it ended up making the MainActivity class abstract
abstract class MainActivity : AppCompatActivity()
{
/// code base
}
instead of
class MainActivity : AppCompatActivity()
{
// code base
}

Related

Class Not Found When Unmarshalling: com.example.Parcelable.User

I'm facing following Errors/Exceptions when receiving data from one App to another App in Kotlin (Android):
Class Not Found When Unmarshalling: com.example.Parcelable.User
mExtras=Bundle[{)}]
mParcelledData=null
mMap=size 0
Also, In MainActivtiy: if(intent.hasExtra("USER")) statement giving error and displaying Toast message written in Else statement.
Please help me how I can resolve these exceptions ? Thanks a Lot!
(SendingApp File) -> MainActivity.kt
btnSendData1.setOnClickListener {
val intent = Intent(Intent.ACTION_SEND).setType("text/plain")
intent.component =
ComponentName.unflattenFromString("com.example.receive")
val args = Bundle()
args.putParcelable(USER_KEY, User("Ahmer", "28"))
intent.putExtras(args)
Log.d("Ahmer", args.toString())
startActivity(Intent.createChooser(intent, "Share to..."))
}
(ReceivingApp File) -> MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var textView: TextView
#SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.tv_username)
when (intent?.action) {
Intent.ACTION_SEND -> {
if ("text/plain" == intent.type) {
handleSendText(intent)
} else {
Toast.makeText(this, "No Text", Toast.LENGTH_SHORT).show()
}
}
}
}
#SuppressLint("SetTextI18n")
private fun handleSendText(intent: Intent) {
if (intent.extras != null) {
if (intent.hasExtra("USER")) {
Log.d("Intent", intent.toString())
val bundle = intent.extras
val user = bundle?.getParcelable<User>("USER") as User
textView.text = "${user.name}${user.age}"
} else {
Toast.makeText(this, "Has Extras Null", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "Extras Null", Toast.LENGTH_SHORT).show()
}
}
}
(ReceivingApp File) -> User.kt
package com.example.receive
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
#Parcelize
data class User(
var name: String,
var age: String
) : Parcelable

How to scan barcode lines with VARCHAR Chars For example OfficeEqp35023U11 In android Kotlin without spaces

How to scan barcode lines with VARCHAR Chars For example OfficeEqp35023U11 In android Kotlin
Struggling to scan this asset image
Struggling to scan this asset image
class BarcodeScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
private var mScannerView: ZXingScannerView? = null
lateinit var apiInterface: APIInterface
public override fun onCreate(state: Bundle?) {
super.onCreate(state)
setContentView(R.layout.activity_barcode_scanner)
ssoId = intent.getStringExtra("initiator")
val contentFrame = findViewById<View>(R.id.content_frame) as ViewGroup
mScannerView = ZXingScannerView(this)
contentFrame.addView(mScannerView)
}
public override fun onResume() {
super.onResume()
mScannerView!!.setResultHandler(this)
mScannerView!!.startCamera()
}
public override fun onPause() {
super.onPause()
mScannerView!!.stopCamera()
}
override fun handleResult(rawResult: Result) {
Toast.makeText(
this, "Contents = " + rawResult.text +
", Format = " + rawResult.barcodeFormat.toString(), Toast.LENGTH_SHORT
).show()
var result = rawResult.text
if (result.isDigitsOnly()) {
assetTag = result.filter { it.isLetterOrDigit() }
mScannerView!!.stopCamera()
apiInterface = APIClient.client!!.create(APIInterface::class.java)
val call: Call<Verifiedd> =
apiInterface.saveVerification(assetTag, ssoId, ssoId, "Verified", "Verified")
call.enqueue(object : Callback<Verifiedd> {
override fun onResponse(call: Call<Verifiedd>, response: Response<Verifiedd>) {
if (response.body() != null) {
lovelyProgressDialog?.dismiss()
Log.d("TAG", response.code().toString() + "")
var displayResponse = ""
val resource: Verifiedd = response.body()!!
responseCode = resource.responseCode
responseMessage = resource.responseMessage
if (responseMessage == "Data persisted successfully " || responseMessage.equals(
"Data persisted successfully "
)
) {
Toasty.normal(
this#BarcodeScannerActivity,
"",
Toasty.LENGTH_LONG
).show()
} else if (responseMessage == "" || responseMessage.equals(
""
)
) {
Toasty.normal(
this#BarcodeScannerActivity,
"Invalid Asset Verification status values",
Toasty.LENGTH_LONG
).show()
val intent =
Intent(this#BarcodeScannerActivity, BranchItemsActivity::class.java)
intent.flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
intent.putExtra(
"initiator", ssoId
)
startActivity(intent)
}
}
}
override fun onFailure(call: Call<Verifiedd>, t: Throwable) {
Toasty.normal(
this#BarcodeScannerActivity,
"Server Error",
Toasty.LENGTH_LONG
).show()
}
})

How to get variable value into another function in kotlin

I'm assigning a value to lateinit varable in my first function and I want to access same value in my second function but gives me an error UninitializedPropertyAccessException. I know it is because of variable scope. My question is how can i access this value?
Here is my lateinit variables
lateinit var area:String
lateinit var zipcode:String
Here is my first function
fun showSearchDialog(view: View) {
val dialog = context?.let { Dialog(it) }
dialog?.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog?.setCancelable(true)
dialog?.setContentView(R.layout.alertdialog_search_layout)
//Initializing the views of the dialog.
val postalCode: TextInputEditText? = dialog?.findViewById(R.id.et_zip_code)
val sliderArea: Slider? = dialog?.findViewById(R.id.slider_area)
val searchButton: Button? = dialog?.findViewById(R.id.search_btn)
searchButton?.setOnClickListener {
if (sliderArea != null) {
area = sliderArea.value.toString()
}
zipcode = postalCode?.text.toString()
val postCodeUpperCase = postalCode?.text.toString().toUpperCase(Locale.ROOT)
if (zipcode.isEmpty()) {
postalCode?.error = "Please enter your post code"
postalCode?.requestFocus()
} else if (isValidZipCode(postCodeUpperCase)) {
postalCode?.error = "Please enter valid post code"
postalCode?.requestFocus()
} else {
if (isNetworkAvailable(requireContext())) {
viewModel.getSkipFilterList(zipcode, area)
Toast.makeText(context, "Valid postal code is = $zipcode", Toast.LENGTH_LONG).show()
}
else {
showAlertDialog(getString(R.string.no_internet))
}
}
//dialog.dismiss()
}
dialog?.show()
Here is my second Function
override fun inOnCreateView(mRootView: ViewGroup, savedInstanceState: Bundle?) {
val homeActivity = activity as HomeNavHostActivity
homeActivity.toolbar_id?.visibility = View.VISIBLE
homeActivity.toolbar_search_icon_id.visibility = View.VISIBLE
homeActivity.toolbar_add_icon_id.visibility = View.GONE
homeActivity.home_view_layout?.visibility = View.VISIBLE
homeActivity.bottom_layout?.visibility = View.VISIBLE
homeActivity.toolbar_title_tv.text = "Home"
homeActivity.toolbar_search_icon_id.setOnClickListener() {
showSearchDialog(mRootView)
}
homeActivity.cancel_text.setOnClickListener() {
homeActivity.search_layout.visibility = View.GONE
homeActivity.toolbar_title_tv.visibility = View.VISIBLE
homeActivity.search_view?.setQuery("", false)
homeActivity.search_view?.clearFocus()
}
val dialogHelper by inject<MaterialDialogHelper>()
setupProgressDialog(viewModel.showHideProgressDialog, dialogHelper)
if (isNetworkAvailable(requireContext())) {
viewModel.getSkipFilterList(zipcode, area)
// viewModel.getSkipHomeData()
} else {
showAlertDialog(getString(R.string.no_internet))
}
attachViewModel()
}

RecyclerView Adapter methods not getting called kotlin

In the Adapter method. i am able to print list elements in updateUsers(newUsers: List)
that means I am getting dataset in the Adapter, but none of the Adapter methods are getting called.
I have set the layout manager too.
I have breakpoints for All adapter methods onCreateViewHolder, onBindViewHolder. but control is not entering code block.
class MainActivity : AppCompatActivity() {
lateinit var viewModel: ListViewModel
private val usersAdapter = UserListAdapter(arrayListOf(),this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(ListViewModel::class.java)
viewModel.refresh()
usersList.apply {
layoutManager = LinearLayoutManager(this.context)
adapter = usersAdapter
}
usersList.adapter = usersAdapter
observeViewModel()
}
fun observeViewModel() {
viewModel.users.observe(this, Observer {
it?.let {
it.forEach {
Log.d("Each Item", it.toString());
}
loading_view.visibility = View.VISIBLE
usersAdapter.updateUsers(it)
}
})
viewModel.userLoadError.observe(this, Observer {
it?.let {
list_error.visibility = if (it) View.VISIBLE else View.GONE
}
})
viewModel.loading.observe(this, Observer {
it?.let {
loading_view.visibility = if (it) View.VISIBLE else View.GONE
if (it) {
list_error.visibility = View.GONE
usersList.visibility = View.GONE
}
}
})
}
}
class UserListAdapter(private var users: ArrayList<User>, private val context : Context) : RecyclerView.Adapter<UserListAdapter.UserViewHolder>() {
fun updateUsers(newUsers: List<User>) {
newUsers.forEach() {
Log.d("updateUsers", it.firstName)
}
users.clear()
users.addAll(newUsers)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : UserViewHolder{
val layoutInflator = LayoutInflater.from(parent.context)
val view = layoutInflator.inflate(R.layout.item_layout,parent,false)
return UserViewHolder(view)
}
override fun getItemCount():Int {
return users.size
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(users[position])
}
class UserViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val imageView = view.imageView
private val userName = view.name
private val userEmail = view.email
fun bind(country: User) {
userName.text = country.firstName + " " + country.lastName
userEmail.text = country.email
imageView.loadImage(country.avatar)
}
}
}
I am not exactly sure why your code is not working, here I have implemented using your code and it's working.
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: ListViewModel
private val usersAdapter = UserListAdapter(arrayListOf(), this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(ListViewModel::class.java)
viewModel.refresh()
usersList.apply {
layoutManager = LinearLayoutManager(this.context,
RecyclerView.VERTICAL, false)
adapter = usersAdapter
}
// usersList.adapter = usersAdapter // Don't need, you already set the
// adapter above.
observeViewModel()
}
private fun observeViewModel() {
viewModel.users.observe(this, Observer {
it?.let {
it.forEach {
Log.d("Each Item", it.toString());
}
//loading_view.visibility = View.VISIBLE
usersAdapter.updateUsers(it)
}
})
// commented for simplicity
//viewModel.userLoadError.observe(this, Observer {
// it?.let {
//list_error.visibility = if (it) View.VISIBLE else View.GONE
// }
//})
// viewModel.loading.observe(this, Observer {
// it?.let {
// loading_view.visibility = if (it) View.VISIBLE else View.GONE
// if (it) {
// list_error.visibility = View.GONE
// usersList.visibility = View.GONE
// }
//
// }
// })
}
}
UserListAdapter
class UserListAdapter(private var users: ArrayList<User>, private val context : Context) : RecyclerView.Adapter<UserListAdapter.UserViewHolder>() {
fun updateUsers(newUsers: List<User>) {
newUsers.forEach() {
Log.d("updateUsers", it.firstName)
}
users.clear()
users.addAll(newUsers)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : UserViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.item_layout,parent,false)
return UserViewHolder(view)
}
override fun getItemCount():Int {
return users.size
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bind(users[position])
}
class UserViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val imageView = view.imageView
private val userName = view.name
private val userEmail = view.email
fun bind(country: User) {
userName.text = country.firstName + " " + country.lastName
userEmail.text = country.email
//imageView.loadImage(country.avatar)
}
}
}
ListViewModel
class ListViewModel: ViewModel() {
val users : MutableLiveData<List<User>> = MutableLiveData()
fun refresh() {
val userList = mutableListOf<User>()
userList.add(User(
"User1 First Name",
"User1 last Name",
"example#gmail.com"
))
userList.add(User(
"User2 First Name",
"User2 First Name",
"example#gmail.com"
))
userList.add(User(
"User3 First Name",
"User3 First Name",
"example#gmail.com"
))
userList.add(User(
"User4 First Name",
"User4 First Name",
"example#gmail.com"
))
userList.add(User(
"User5 First Name",
"User5 First Name",
"example#gmail.com"
))
userList.add(User(
"User6 First Name",
"User6 First Name",
"example#gmail.com"
))
users.value = userList
}
}
Hope this helps.

How to sync TabLayout with Recyclerview?

I have a TabLayout with Recyclerview so that when tabs are clicked then the Recyclerview is scrolled to a particular position.
I want the reverse procedure as well such that when the Recyclerview is scrolled to a particular position then the particular tab is highlighted.
For example: If there are 4 tabs in the TabLayout and when Recyclerview is scrolled to 5th position (item visible and below TabLayout) then 3rd tab should be highlighted.
Here when 'How it works' appears below TabLayout then tabs 'How it works' should be highlighted.
Try this
follow this steps
Add a ScrollListener to your RecyclerView
than find first visible item of your RecyclerView
set the select the tab in TabLayout as per position of your RecyclerView
SAMPLE CODE
myRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int itemPosition=linearLayoutManager.findFirstCompletelyVisibleItemPosition();
if(itemPosition==0){ // item position of uses
TabLayout.Tab tab = myTabLayout.getTabAt(Index);
tab.select();
}else if(itemPosition==1){// item position of side effects
TabLayout.Tab tab = myTabLayout.getTabAt(Index);
tab.select();
}else if(itemPosition==2){// item position of how it works
TabLayout.Tab tab = myTabLayout.getTabAt(Index);
tab.select();
}else if(itemPosition==3){// item position of precaution
TabLayout.Tab tab = myTabLayout.getTabAt(Index);
tab.select();
}
}
});
EDIT
public class MyActivity extends AppCompatActivity {
RecyclerView myRecyclerView;
TabLayout myTabLayout;
LinearLayoutManager linearLayoutManager;
ArrayList<String> arrayList = new ArrayList<>();
DataAdapter adapter;
private boolean isUserScrolling = false;
private boolean isListGoingUp = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
myTabLayout = findViewById(R.id.myTabLayout);
myRecyclerView = findViewById(R.id.myRecyclerView);
linearLayoutManager = new LinearLayoutManager(this);
myRecyclerView.setLayoutManager(linearLayoutManager);
myRecyclerView.setHasFixedSize(true);
for (int i = 0; i < 120; i++) {
arrayList.add("Item " + i);
}
adapter= new DataAdapter(this,arrayList);
myRecyclerView.setAdapter(adapter);
myTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
isUserScrolling = false ;
int position = tab.getPosition();
if(position==0){
myRecyclerView.smoothScrollToPosition(0);
}else if(position==1){
myRecyclerView.smoothScrollToPosition(30);
}else if(position==2){
myRecyclerView.smoothScrollToPosition(60);
}else if(position==3){
myRecyclerView.smoothScrollToPosition(90);
}
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
myRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
isUserScrolling = true;
if (isListGoingUp) {
//my recycler view is actually inverted so I have to write this condition instead
if (linearLayoutManager.findLastCompletelyVisibleItemPosition() + 1 == arrayList.size()) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (isListGoingUp) {
if (linearLayoutManager.findLastCompletelyVisibleItemPosition() + 1 == arrayList.size()) {
Toast.makeText(MyActivity.this, "exeute something", Toast.LENGTH_SHORT).show();
}
}
}
}, 50);
//waiting for 50ms because when scrolling down from top, the variable isListGoingUp is still true until the onScrolled method is executed
}
}
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int itemPosition = linearLayoutManager.findFirstVisibleItemPosition();
if(isUserScrolling){
if (itemPosition == 0) { // item position of uses
TabLayout.Tab tab = myTabLayout.getTabAt(0);
tab.select();
} else if (itemPosition == 30) {// item position of side effects
TabLayout.Tab tab = myTabLayout.getTabAt(1);
tab.select();
} else if (itemPosition == 60) {// item position of how it works
TabLayout.Tab tab = myTabLayout.getTabAt(2);
tab.select();
} else if (itemPosition == 90) {// item position of precaution
TabLayout.Tab tab = myTabLayout.getTabAt(3);
tab.select();
}
}
}
});
}
}
private fun syncTabWithRecyclerView() {
// Move recylerview to the position selected by user
menutablayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
if (!isUserScrolling) {
val position = tab.position
linearLayoutManager.scrollToPositionWithOffset(position, 0)
}
}
override fun onTabUnselected(tab: TabLayout.Tab) {
}
override fun onTabReselected(tab: TabLayout.Tab) {
}
})
// Detect recyclerview position and select tab respectively.
menuRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
isUserScrolling = true
} else if (newState == RecyclerView.SCROLL_STATE_IDLE)
isUserScrolling = false
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (isUserScrolling) {
var itemPosition = 0
if (dy > 0) {
// scrolling up
itemPosition = linearLayoutManager.findLastVisibleItemPosition()
} else {
// scrolling down
itemPosition = linearLayoutManager.findFirstVisibleItemPosition()
}
val tab = menutablayout.getTabAt(itemPosition)
tab?.select()
}
}
})
}
I figured you don't even need those flags and it was enough to override RecyclerView's onScrolled and select tab and scroll to position when tab is selected and that Tab wasn't already selected:
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val llm = recyclerView.layoutManager as LinearLayoutManager
// depending on sections'heights one may want to add more logic
// on how to determine which section to scroll to
val firstCompletePos = llm.findFirstCompletelyVisibleItemPosition()
if (firstCompletePos != tabLayout.selectedTabPosition)
tabLayout.getTabAt(firstCompletePos)?.select()
}
Then I have a TextView which is set as customView to tabLayout:
tabLayout.addTab(newTab().also { tab ->
tab.customView = AppCompatTextView(context).apply {
// set layout params match_parent, so the entire section is clickable
// set style, gravity, text etc.
setOnClickListener {
tabLayout.selectTab(tab)
recyclerView.apply {
val scrollTo = tabLayout.selectedTabPosition
smoothScrollToPosition(scrollTo)
}
}
}
})
With this setup you have:
Tab selected when user both scrolls and flings
RecyclerView is scrolled when user clicks on the tab.
I use information from another answers but here is some omissions in code, that makes it not complete and bad working. My solution 100% work without lags. Photo of complete screen you may see in the end.
This is the code inside Fragment:
private val layoutManager get() = recyclerView?.layoutManager as? LinearLayoutManager
/**
* [SmoothScroller] need for smooth scrolling inside [tabListener] of [recyclerView]
* to top border of [RecyclerView.ViewHolder].
*/
private val smoothScroller: SmoothScroller by lazy {
object : LinearSmoothScroller(context) {
override fun getVerticalSnapPreference(): Int = SNAP_TO_START
}
}
/**
* Variable for prevent calling of [RecyclerView.OnScrollListener.onScrolled]
* inside [scrollListener], when user click on [TabLayout.Tab] and
* [tabListener] was called.
*
* Fake calls happens because of [tabListener] have smooth scrolling to position,
* and when [scrollListener] catch scrolling and call [TabLayout.Tab.select].
*/
private var isTabClicked = false
/**
* Variable for prevent calling of [TabLayout.OnTabSelectedListener.onTabSelected]
* inside [tabListener], when user scroll list and function
* [RecyclerView.OnScrollListener.onScrolled] was called inside [scrollListener].
*
* Fake calls happens because [scrollListener] contains call of [TabLayout.Tab.select],
* which in turn calling click handling inside [tabListener].
*/
private var isScrollSelect = false
private val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
/**
* Reset [isTabClicked] key when user start scroll list.
*/
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
isTabClicked = false
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
/**
* Prevent scroll handling after tab click (see inside [tabListener]).
*/
if (isTabClicked) return
val commonIndex = commonIndex ?: return
val karaokeIndex = karaokeIndex ?: return
val socialIndex = socialIndex ?: return
val reviewIndex = reviewIndex ?: return
val addIndex = addIndex ?: return
when (layoutManager?.findFirstVisibleItemPosition() ?: return) {
in commonIndex until karaokeIndex -> selectTab(TabIndex.COMMON)
in karaokeIndex until socialIndex -> selectTab(TabIndex.KARAOKE)
in socialIndex until reviewIndex -> {
/**
* In case if [reviewIndex] can't reach top of the list,
* to become first visible item. Need check [addIndex]
* (last element of list) completely visible or not.
*/
if (layoutManager?.findLastCompletelyVisibleItemPosition() != addIndex) {
selectTab(TabIndex.CONTACTS)
} else {
selectTab(TabIndex.REVIEWS)
}
}
in reviewIndex until addIndex -> selectTab(TabIndex.REVIEWS)
}
}
/**
* It's very important to skip cases when [TabLayout.Tab] is checked like current,
* otherwise [tabLayout] will terribly lagging on [recyclerView] scroll.
*/
private fun selectTab(#TabIndex index: Int) {
val tab = tabLayout?.getTabAt(index) ?: return
if (!tab.isSelected) {
recyclerView?.post {
isScrollSelect = true
tab.select()
}
}
}
}
private val tabListener = object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) = scrollToPosition(tab)
override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
/*
* If user click on tab again.
*/
override fun onTabReselected(tab: TabLayout.Tab?) = scrollToPosition(tab)
private fun scrollToPosition(tab: TabLayout.Tab?) {
/**
* Prevent scroll to position calling from [scrollListener].
*/
if (isScrollSelect) {
isScrollSelect = false
return
}
val position = when (tab?.position) {
TabIndex.COMMON -> commonIndex
TabIndex.KARAOKE -> karaokeIndex
TabIndex.CONTACTS -> socialIndex
TabIndex.REVIEWS -> reviewIndex
else -> null
}
if (position != null) {
isTabClicked = true
smoothScroller.targetPosition = position
layoutManager?.startSmoothScroll(smoothScroller)
}
}
}
private val commonIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.Info }
private val karaokeIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.Karaoke }
private val socialIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.Social }
private val reviewIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.ReviewHeader }
private val addIndex get() = adapter.list.validIndexOfFirst { it is ClubScreenItem.AddReview }
Extension:
private const val ND_INDEX = -1
fun <T> List<T>.validIndexOfFirst(predicate: (T) -> Boolean): Int? {
return indexOfFirst(predicate).takeIf { it != ND_INDEX }
}
TabIndex class for getting tab by position:
#IntDef(TabIndex.COMMON, TabIndex.KARAOKE, TabIndex.CONTACTS, TabIndex.REVIEWS)
private annotation class TabIndex {
companion object {
const val COMMON = 0
const val KARAOKE = 1
const val CONTACTS = 2
const val REVIEWS = 3
}
}
And it's how looks my ClubScreenItem:
sealed class ClubScreenItem {
class Info(val data: ClubItem): ClubScreenItem()
...
class Karaoke(...): ClubScreenItem()
class Social(...): ClubScreenItem()
...
class ReviewHeader(...): ClubScreenItem()
...
object AddReview : ClubScreenItem()
}
This is how looks screen:
Try this,
Simple Step :
Detect RecyclerView scroll state
Use findFirstVisibleItemPosition() to return the adapter position of the first visible view
Change the tab based on RecyclerView item position
Done
private fun syncTabWithRecyclerView() {
var isUserScrolling = false
val layoutManager = binding.recyclerViewGroup.layoutManager as LinearLayoutManager
val tabListener = object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
val tabPosition = tab?.position
if (tabPosition != null) {
viewModel.setTabPosition(tabPosition)
// prevent RecyclerView to snap to its item start position while user scrolling,
// idk how to explain this XD
if (!isUserScrolling){
layoutManager.scrollToPositionWithOffset(tabPosition, 0)
}
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabReselected(tab: TabLayout.Tab?) {}
}
binding.tabLayout.addOnTabSelectedListener(tabListener)
// Detect recyclerview scroll state
val onScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
isUserScrolling = true
} else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
isUserScrolling = false
}
}
// this just represent my tab name using enum class ,
// and ordinal is just the index of its position in enum
val hardcase3D = CaseType.HARDCASE_3D.ordinal
val softcaseBlackmatte = CaseType.SOFTCASE_BLACKMATTE.ordinal
val softcaseTransparent = CaseType.SOFTCASE_TRANSPARENT.ordinal
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (isUserScrolling) {
when (layoutManager.findFirstVisibleItemPosition()) {
in hardcase3D until softcaseBlackmatte -> {
viewModel.setTabPosition(hardcase3D)
}
in softcaseBlackmatte until softcaseTransparent -> {
viewModel.setTabPosition(softcaseBlackmatte)
}
softcaseTransparent -> {
viewModel.setTabPosition(softcaseTransparent)
}
}
}
}
}
binding.recyclerViewGroup.addOnScrollListener(onScrollListener)
}
viewModel , you can simply use liveData if you want
private var _tabPosition = MutableStateFlow(CaseType.HARDCASE_3D)
val tabPostition : StateFlow<CaseType>
get() = _tabPosition
fun setTabPosition(position: Int){
_tabPosition.value = CaseType.values()[position]
}
Observer
lifecycleScope.launch(Dispatchers.Default) {
viewModel.tabPostition.collect { caseType ->
val positionIndex = CaseType.values().indexOf(caseType)
handleSelectedTab(positionIndex)
}
}
and handleSelectedTab
private fun handleSelectedTab(index: Int) {
val tab = binding.tabLayout.getTabAt(index)
tab?.select()
}
enum
enum class CaseType(val caseTypeName:String) {
HARDCASE_3D("Hardcase 3D"),
SOFTCASE_BLACKMATTE("Softcase Blackmatte"),
SOFTCASE_TRANSPARENT("Softcase Transparent")
}

Categories

Resources