I am new to Android and Mapbox and I was wondering how to use the user location and Mapbox.
I want to make an app that can track my location on an Android device with Mapbox.
When I use Mapbox's example code and then run the emulator (Android Studio), I cannot see my location. The emulator does ask for my location. I don't quite understand what I'm doing wrong.
AndroidManifest.kt
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
MainActivity.kt
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import com.mapbox.android.gestures.MoveGestureDetector
import com.mapbox.maps.CameraOptions
import com.mapbox.maps.MapView
import com.mapbox.maps.Style
import com.mapbox.maps.extension.style.expressions.dsl.generated.interpolate
import com.mapbox.maps.plugin.LocationPuck2D
import com.mapbox.maps.plugin.gestures.OnMoveListener
import com.mapbox.maps.plugin.gestures.gestures
import com.mapbox.maps.plugin.locationcomponent.OnIndicatorBearingChangedListener
import com.mapbox.maps.plugin.locationcomponent.OnIndicatorPositionChangedListener
import com.mapbox.maps.plugin.locationcomponent.location
import java.lang.ref.WeakReference
class MainActivity : AppCompatActivity() {
private lateinit var locationPermissionHelper: LocationPermissionHelper
private val onIndicatorBearingChangedListener = OnIndicatorBearingChangedListener {
mapView.getMapboxMap().setCamera(CameraOptions.Builder().bearing(it).build())
}
private val onIndicatorPositionChangedListener = OnIndicatorPositionChangedListener {
mapView.getMapboxMap().setCamera(CameraOptions.Builder().center(it).build())
mapView.gestures.focalPoint = mapView.getMapboxMap().pixelForCoordinate(it)
}
private val onMoveListener = object : OnMoveListener {
override fun onMoveBegin(detector: MoveGestureDetector) {
onCameraTrackingDismissed()
}
override fun onMove(detector: MoveGestureDetector): Boolean {
return false
}
override fun onMoveEnd(detector: MoveGestureDetector) {}
}
private lateinit var mapView: MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mapView = findViewById(R.id.mapView)
mapView?.getMapboxMap()?.loadStyleUri(Style.MAPBOX_STREETS)
locationPermissionHelper = LocationPermissionHelper(WeakReference(this))
locationPermissionHelper.checkPermissions {
onMapReady()
}
}
private fun onMapReady() {
mapView.getMapboxMap().setCamera(
CameraOptions.Builder()
.zoom(14.0)
.build()
)
mapView.getMapboxMap().loadStyleUri(
Style.MAPBOX_STREETS
) {
initLocationComponent()
setupGesturesListener()
}
}
private fun setupGesturesListener() {
mapView.gestures.addOnMoveListener(onMoveListener)
}
private fun initLocationComponent() {
val locationComponentPlugin = mapView.location
locationComponentPlugin.updateSettings {
this.enabled = true
this.locationPuck = LocationPuck2D(
bearingImage = AppCompatResources.getDrawable(
this#MainActivity,
R.drawable.ic_baseline_location_on_24,
),
shadowImage = AppCompatResources.getDrawable(
this#MainActivity,
R.drawable.ic_baseline_location_on_24,
),
scaleExpression = interpolate {
linear()
zoom()
stop {
literal(0.0)
literal(0.6)
}
stop {
literal(20.0)
literal(1.0)
}
}.toJson()
)
}
locationComponentPlugin.addOnIndicatorPositionChangedListener(onIndicatorPositionChangedListener)
locationComponentPlugin.addOnIndicatorBearingChangedListener(onIndicatorBearingChangedListener)
}
private fun onCameraTrackingDismissed() {
Toast.makeText(this, "onCameraTrackingDismissed", Toast.LENGTH_SHORT).show()
mapView.location
.removeOnIndicatorPositionChangedListener(onIndicatorPositionChangedListener)
mapView.location
.removeOnIndicatorBearingChangedListener(onIndicatorBearingChangedListener)
mapView.gestures.removeOnMoveListener(onMoveListener)
}
override fun onDestroy() {
super.onDestroy()
mapView.location
.removeOnIndicatorBearingChangedListener(onIndicatorBearingChangedListener)
mapView.location
.removeOnIndicatorPositionChangedListener(onIndicatorPositionChangedListener)
mapView.gestures.removeOnMoveListener(onMoveListener)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
locationPermissionHelper.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onStart() {
super.onStart()
mapView?.onStart()
}
override fun onStop() {
super.onStop()
mapView?.onStop()
}
override fun onLowMemory() {
super.onLowMemory()
mapView?.onLowMemory()
}
}
LocationHelper.kt
import android.app.Activity
import android.widget.Toast
import com.mapbox.android.core.permissions.PermissionsListener
import com.mapbox.android.core.permissions.PermissionsManager
import java.lang.ref.WeakReference
class LocationPermissionHelper(val activity: WeakReference<Activity>) {
private lateinit var permissionsManager: PermissionsManager
fun checkPermissions(onMapReady: () -> Unit) {
if (PermissionsManager.areLocationPermissionsGranted(activity.get())) {
onMapReady()
} else {
permissionsManager = PermissionsManager(object : PermissionsListener {
override fun onExplanationNeeded(permissionsToExplain: List<String>) {
Toast.makeText(
activity.get(), "You need to accept location permissions.",
Toast.LENGTH_SHORT
).show()
}
override fun onPermissionResult(granted: Boolean) {
if (granted) {
onMapReady()
} else {
activity.get()?.finish()
}
}
})
permissionsManager.requestLocationPermissions(activity.get())
}
}
fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
Emulator
Image of Emulator
Related
The app must show all product but show only last (number 6). when i launch app, work, but return this error : E/RecyclerView: No adapter attached; skipping layout. I'm beginner in android language and searching online i didn't find nothing.
the app was composed from mainActivity and CartActivity
output
this is realtime DB
this is main code
package com.example.tpsi_maccagnola
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.GridLayoutManager
import com.example.tpsi_maccagnola.events.UpdateCarrello
import com.example.tpsi_maccagnola.listener.cartlistener
import com.example.tpsi_maccagnola.listener.foodListener
import com.example.tpsi_maccagnola.modelli.Carrello
import com.example.tpsi_maccagnola.modelli.Food
import com.google.android.material.snackbar.Snackbar
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
import kotlinx.android.synthetic.main.activity_main.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class MainActivity : AppCompatActivity(), foodListener, cartlistener {
lateinit var foodListener: foodListener
lateinit var cartlistener1: cartlistener
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
super.onStop()
if (EventBus.getDefault().hasSubscriberForEvent(UpdateCarrello::class.java))
EventBus.getDefault().removeStickyEvent(UpdateCarrello::class.java)
EventBus.getDefault().unregister(this)
}
#Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onUpdateCart(event: UpdateCarrello) {
counCartDB()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
init()
caricaDB()
counCartDB()
}
private fun counCartDB() {
val cartmodels: MutableList<Carrello> = ArrayList()
FirebaseDatabase.getInstance()
.getReference("carrello")
.child("UNIQUE_USER_ID")
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (cartsnapshot in snapshot.children) {
val cartmodel = cartsnapshot.getValue(Carrello::class.java)
cartmodel!!.key = cartsnapshot.key
cartmodels.add(cartmodel)
}
cartlistener1.onCartSuccess(cartmodels)
}
override fun onCancelled(error: DatabaseError) {
cartlistener1.onCartFailed(error.message)
}
})
}
private fun caricaDB() {
val foods: MutableList<Food> = ArrayList()
FirebaseDatabase.getInstance()
.getReference("Food").addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
for (FoodSnapshot in snapshot.children) {
val foodModel = FoodSnapshot.getValue(Food::class.java)
foodModel!!.key = FoodSnapshot.key
foods.add(foodModel)
}
foodListener.onloadsuccess(foods)
} else {
foodListener.onloadfailed("non esiste")
}
}
override fun onCancelled(error: DatabaseError) {
foodListener.onloadfailed(error.message)
}
})
}
private fun init() {
foodListener = this
cartlistener1 = this
val gridLayoutManager = GridLayoutManager(this, 2)
food_recycler.layoutManager = gridLayoutManager
food_recycler.addItemDecoration(decoration())
btnCart.setOnClickListener{ startActivity(Intent(this,CartActivity::class.java))}
}
override fun onloadsuccess(foodModelList: List<Food>?) {
val adapter = FoodAdapter(this, foodModelList!!, cartlistener1)
food_recycler.adapter = adapter
}
override fun onloadfailed(message: String?) {
Snackbar.make(layoutprincipale, message!!, Snackbar.LENGTH_LONG).show()
}
override fun onCartSuccess(carrelloList: List<Carrello>) {
var cartTot = 0
for (cartlist in carrelloList!!) {
cartTot += cartlist!!.quantita
/*badge!!.setNumber(cartTot)*/
}
}
override fun onCartFailed(message: String?) {
Snackbar.make(layoutprincipale, message!!, Snackbar.LENGTH_LONG).show()
}
}
this is adapter
package com.example.tpsi_maccagnola
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.tpsi_maccagnola.events.UpdateCarrello
import com.example.tpsi_maccagnola.listener.RecyclerListener
import com.example.tpsi_maccagnola.listener.cartlistener
import com.example.tpsi_maccagnola.modelli.Carrello
import com.example.tpsi_maccagnola.modelli.Food
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
import org.greenrobot.eventbus.EventBus
class FoodAdapter(
private val context: Context,
private val list: List<Food>,
private val cartlistener1: cartlistener
) : RecyclerView.Adapter<FoodAdapter.FoodHolder>() {
class FoodHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
var imageView: ImageView?= null
var txtName: TextView?= null
var txtprice: TextView?= null
private var clickListener: RecyclerListener? = null
fun setClickListener (clickListener: RecyclerListener){
this.clickListener = clickListener;
}
init {
imageView = itemView.findViewById(R.id.imageView) as ImageView;
txtName = itemView.findViewById(R.id.txtName) as TextView;
txtprice = itemView.findViewById(R.id.txtprice) as TextView;
itemView.setOnClickListener(this)
}
override fun onClick(p0: View?) {
clickListener!!.onclcickProdotto(p0,adapterPosition )
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FoodHolder {
return FoodHolder(LayoutInflater.from(context).inflate(R.layout.layout_food,parent,false))
}
override fun onBindViewHolder(holder: FoodHolder, position: Int) {
Glide.with(context).load(list[position].image).into(holder.imageView!!)
holder.txtName!!.text =StringBuilder().append(list[position].name)
holder.txtprice!!.text =StringBuilder("€").append(list[position].price)
holder.setClickListener(object: RecyclerListener{
override fun onclcickProdotto(view: View?, position: Int) {
aggiungiCarrello(list[position])
}
} )
}
private fun aggiungiCarrello(food: Food) {
val utenteCart = FirebaseDatabase.getInstance().getReference("Cart").child("UNIQUE_USER_ID")
utenteCart.child(Food.key!!).addListenerForSingleValueEvent(object :ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
if(snapshot.exists()){ //se è gia presente aggiorno
val cartmodel = snapshot.getValue(Carrello::class.java)
val updatecart: MutableMap<String,Any> = HashMap()
cartmodel!!.quantita = cartmodel!!.quantita+1
updatecart["quantita"] = cartmodel!!.quantita
updatecart["totale"] =cartmodel!!.quantita * cartmodel.price!!.toFloat()
utenteCart.child(food.key!!)
.updateChildren(updatecart)
.addOnSuccessListener {
EventBus.getDefault().postSticky(UpdateCarrello())
cartlistener1.onCartFailed("aggiunto correttamente")
}.addOnFailureListener{e -> cartlistener1.onCartFailed(e.message)
}
}else{
val carrello1 = Carrello
carrello1.key = food.key
carrello1.name = food.name
carrello1.image = food.image
carrello1.price = food.price
carrello1.quantita = 1
carrello1.totale = food.price!!.toFloat()
utenteCart.child(food.key!!)
.setValue(carrello1)
.addOnSuccessListener {
EventBus.getDefault().postSticky(UpdateCarrello())
cartlistener1.onCartFailed("aggiunto correttamente")
}.addOnFailureListener{e -> cartlistener1.onCartFailed(e.message)
}
}
}
override fun onCancelled(error: DatabaseError) {
cartlistener1.onCartFailed(error.message)
}
})
}
override fun getItemCount(): Int {
return list.size
}
}
I am trying to build an app that uses the youtube API. I need to call the search API, whenever the user searches for something and then display the results in a new activity(SearchResultsActivity). However the scenario I get is that my app, rather than opening the SearchResultsActivity gets redirected to the MainActivity.
The scenario is recorded, and can be better understood through this https://drive.google.com/file/d/1WESzXbRbjhmKCY1gfUq1d2gllIEsILCf/view?usp=sharing.
I want to know what is causing the SearchResultsActivity to be paused by itself and MainActivity being restarted instead. I tried logging the lifecycles to know what was happening and I found this:
Also Search box activity here means the SearchActivity:
package com.example.mytube.UI
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.widget.EditText
import android.widget.ImageView
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.mytube.R
import com.example.mytube.adapters.SearchedHistoryAdapter
import com.example.mytube.db.SearchDatabase
import com.example.mytube.repository.VideosRepository
class SearchActivity : AppCompatActivity() {
lateinit var searchAdapter: SearchedHistoryAdapter
lateinit var viewModel: VideosViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search)
val repository = VideosRepository(SearchDatabase.getSearchDatabase(this))
val viewModelFactory = VideosViewModelProviderFactory(repository)
viewModel = ViewModelProvider(this,viewModelFactory).get(VideosViewModel::class.java)
val recyclerView = findViewById<RecyclerView>(R.id.searched_terms)
searchAdapter = SearchedHistoryAdapter()
recyclerView.apply {
adapter = searchAdapter
layoutManager = LinearLayoutManager(this#SearchActivity)
}
val backButton = findViewById<ImageView>(R.id.ivGoback)
backButton.apply {
setOnClickListener {
finish()
}
}
val searchBar = findViewById<EditText>(R.id.search_box)
val searchBtn = findViewById<ImageView>(R.id.ivSearch)
searchBtn.setOnClickListener {
if (searchBar.text.toString().isNotEmpty()) {
viewModel.insertSearchItem(searchBar.text.toString())
val intent = Intent(applicationContext, SearchResultsActivity::class.java)
intent.putExtra("searchQuery", searchBar.text)
startActivity(intent)
}
}
searchBar.setOnKeyListener(View.OnKeyListener{v, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
if (searchBar.text.toString().isNotEmpty()) {
viewModel.insertSearchItem(searchBar.text.toString())
}
return#OnKeyListener true
}
return#OnKeyListener false
})
viewModel.getSearchHistory().observe(this, Observer { searchItems ->
searchAdapter.differ.submitList(searchItems)
})
}
override fun onPause() {
super.onPause()
Log.d("searched", "Search box activity paused")
}
override fun onStop() {
super.onStop()
Log.d("searched", "Search box activity stopped")
}
override fun onDestroy() {
super.onDestroy()
Log.d("searched", "Search box activity destroyed")
}
}
The SearchResultsActivity:
package com.example.mytube.UI
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.AbsListView
import android.widget.ProgressBar
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.mytube.R
import com.example.mytube.adapters.VideosAdapter
import com.example.mytube.db.SearchDatabase
import com.example.mytube.repository.VideosRepository
import com.example.mytube.util.Resource
class SearchResultsActivity : AppCompatActivity() {
lateinit var viewModel: VideosViewModel
lateinit var videosAdapter: VideosAdapter
var searchQuery = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search_results)
searchQuery = intent.getStringExtra("searchQuery").toString()
val repository = VideosRepository(SearchDatabase.getSearchDatabase(this))
val viewModelFactory = VideosViewModelProviderFactory(repository)
viewModel = ViewModelProvider(this,viewModelFactory).get(VideosViewModel::class.java)
videosAdapter = VideosAdapter(viewModel)
val recyclerView = findViewById<RecyclerView>(R.id.videos)
recyclerView.apply {
layoutManager = LinearLayoutManager(this#SearchResultsActivity)
adapter = videosAdapter
addOnScrollListener(this#SearchResultsActivity.scrollListener)
}
val progressBar = findViewById<ProgressBar>(R.id.paginationProgressBar)
viewModel.getSearchResults(searchQuery)
viewModel.searchResults.observe(this, Observer { resource ->
when(resource) {
is Resource.Success -> {
hideProgressBar(progressBar)
resource.data?.let { videoResponse ->
if (viewModel.nextSearchPageId != videoResponse.nextPageToken) {
viewModel.nextSearchPageId = videoResponse.nextPageToken
viewModel.searchedVideos.addAll(videoResponse.items)
// videoResponse.items.forEach { viewModel.getChannel(it.snippet.channelId) }
Log.d("Channels", viewModel.channels.toString())
videosAdapter.differ.submitList(viewModel.searchedVideos.toList())
}
else {
Log.d("Videos", "next token dekh ${viewModel.nextPageId}")
videosAdapter.differ.submitList(viewModel.searchedVideos.toList())
}
}
}
is Resource.Error -> {
hideProgressBar(progressBar)
Log.e("Videos", resource.message.toString())
}
is Resource.Loading -> {
showProgressBar(progressBar)
}
}
})
viewModel.channelResponse.observe(this, Observer { resource ->
when (resource) {
is Resource.Success -> {
resource.data?.let { channels ->
viewModel.channels.set(channels.items[0].id, channels.items[0])
// videosAdapter.differ.submitList(viewModel.videos.toList())
}
}
is Resource.Error -> {
Log.e("Channels", resource.message.toString())
}
is Resource.Loading -> {
Log.d("Channels", "Waiting")
}
}
})
}
private fun hideProgressBar(bar: ProgressBar) {
bar.visibility = View.INVISIBLE
isLoading = false
}
private fun showProgressBar(bar: ProgressBar){
bar.visibility = View.VISIBLE
isLoading = true
}
var isScrolling = false
var isLoading = false
val scrollListener = object: RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
isScrolling = true
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val manager = recyclerView.layoutManager as LinearLayoutManager
val currentItems = manager.childCount
val totaltItems = manager.itemCount
val scrolledItems = manager.findFirstVisibleItemPosition()
if (isScrolling && totaltItems == currentItems + scrolledItems && !isLoading && viewModel.searchedVideos.size <= 1_000_000) {
viewModel.getNextSearchResults(searchQuery)
isScrolling = false
} else {
videosAdapter.differ.submitList(viewModel.searchedVideos.toList())
recyclerView.setPadding(0, 0, 0, 0)
Log.d("Videos", viewModel.searchedVideos.size.toString())
}
}
}
override fun onPause() {
super.onPause()
Log.d("searched", "Search Results Activity paused")
}
override fun onStop() {
super.onStop()
Log.d("searched", "Search Results Activity stopped")
}
override fun onDestroy() {
super.onDestroy()
Log.d("searched", "Search Results Activity destroyed")
}
}
The logcat shows the following error:
2021-09-28 12:51:06.200 1207-2988/? W/ActivityTaskManager: Force finishing activity com.example.mytube/.UI.SearchResultsActivity
For recreating the error, here is the gitHub repo to the app: https://github.com/AkhilrajNambiar/MyTube-1
Thanks in advance!
Currently i am working in Kotlin android development. while writing the MVVM architecture, i am stuck with ViewModel Creation
Error : Only classes are allowed on the left hand side of a class literal
Related classes are
Detail
BaseViewModel class, while creating the object of this class, we getting the error
package com.logicipher.mvvm.ui.base
import androidx.databinding.ObservableBoolean
import androidx.lifecycle.ViewModel
import com.logicipher.mvvm.data.DataManager
import com.logicipher.mvvm.utils.rx.SchedulerProvider
import io.reactivex.disposables.CompositeDisposable
import java.lang.ref.WeakReference
/**
* Created by Shamji N S on 20-08-2020.
*/
abstract class BaseViewModel<N>(
dataManager: DataManager,
schedulerProvider: SchedulerProvider
) : ViewModel() {
private val mDataManager: DataManager
private val mIsLoading = ObservableBoolean()
private val mSchedulerProvider: SchedulerProvider
private val mCompositeDisposable: CompositeDisposable
private var mNavigator: WeakReference<N>? = null
override fun onCleared() {
mCompositeDisposable.dispose()
super.onCleared()
}
fun getCompositeDisposable(): CompositeDisposable {
return mCompositeDisposable
}
fun getDataManager(): DataManager {
return mDataManager
}
fun getIsLoading(): ObservableBoolean {
return mIsLoading
}
fun setIsLoading(isLoading: Boolean) {
mIsLoading.set(isLoading)
}
fun getNavigator(): N? {
return mNavigator!!.get()
}
fun setNavigator(navigator: N) {
mNavigator = WeakReference(navigator)
}
fun getSchedulerProvider(): SchedulerProvider {
return mSchedulerProvider
}
init {
mDataManager = dataManager
mSchedulerProvider = schedulerProvider
mCompositeDisposable = CompositeDisposable()
}
}
BaseActivity
package com.logicipher.mvvm.ui.base
import android.annotation.TargetApi
import android.app.ProgressDialog
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.inputmethod.InputMethodManager
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import com.logicipher.mvvm.utils.CommonUtils
import com.logicipher.mvvm.utils.NetworkUtils
import dagger.android.AndroidInjection
/**
* Created by Shamji N S on 20-08-2020.
*/
abstract class BaseActivity<T : ViewDataBinding?, V : BaseViewModel<*>?> :
AppCompatActivity(), BaseFragment.Callback {
// TODO
// this can probably depend on isLoading variable of BaseViewModel,
// since its going to be common for all the activities
private var mProgressDialog: ProgressDialog? = null
private var mViewDataBinding: T? = null
private var mViewModel: V? = null
/**
* Override for set binding variable
*
* #return variable id
*/
abstract fun getBindingVariable(): Int
/**
* #return layout resource id
*/
#LayoutRes
abstract fun getLayoutId(): Int
/**
* Override for set view model
*
* #return view model instance
*/
abstract fun getViewModel(): V
override fun onFragmentAttached() {
}
override fun onFragmentDetached(tag: String?) {
}
override fun onCreate(savedInstanceState: Bundle?) {
performDependencyInjection()
super.onCreate(savedInstanceState)
performDataBinding()
}
fun getViewDataBinding(): T? {
return mViewDataBinding
}
#TargetApi(Build.VERSION_CODES.M)
fun hasPermission(permission: String?): Boolean {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
checkSelfPermission(permission!!) == PackageManager.PERMISSION_GRANTED
}
fun hideKeyboard() {
val view = this.currentFocus
if (view != null) {
val imm =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm?.hideSoftInputFromWindow(view.windowToken, 0)
}
}
fun hideLoading() {
if (mProgressDialog != null && mProgressDialog!!.isShowing) {
mProgressDialog!!.cancel()
}
}
fun isNetworkConnected(): Boolean {
return NetworkUtils.isNetworkConnected(applicationContext)
}
fun openActivityOnTokenExpire() {
}
fun performDependencyInjection() {
AndroidInjection.inject(this)
}
#TargetApi(Build.VERSION_CODES.M)
fun requestPermissionsSafely(
permissions: Array<String?>?,
requestCode: Int
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions!!, requestCode)
}
}
fun showLoading() {
hideLoading()
mProgressDialog = CommonUtils.showLoadingDialog(this)
}
private fun performDataBinding() {
mViewDataBinding = DataBindingUtil.setContentView<T>(this, getLayoutId())
mViewModel = if (mViewModel == null) getViewModel() else mViewModel
mViewDataBinding!!.setVariable(getBindingVariable(), mViewModel)
mViewDataBinding!!.executePendingBindings()
}
}
MainActivity
package com.logicipher.mvvm.ui.main
import android.os.Bundle
import android.util.Log
import androidx.lifecycle.ViewModelProviders
import com.logicipher.mvvm.BR
import com.logicipher.mvvm.R
import com.logicipher.mvvm.ViewModelProviderFactory
import com.logicipher.mvvm.databinding.ActivityMainBinding
import com.logicipher.mvvm.ui.base.BaseActivity
import javax.inject.Inject
/**
* Created by Shamji N S on 21-08-2020.
*/
class MainActivity : BaseActivity<ActivityMainBinding?, MainViewModel<MainNavigator>?>(),
MainNavigator /*, HasSupportFragmentInjector*/ {
/* #Inject
DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;*/
#set:Inject
internal var factory: ViewModelProviderFactory? = null
var mViewModel: MainViewModel<MainNavigator>? = null
var mBinding: ActivityMainBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate: ")
mBinding = getViewDataBinding()
mViewModel?.setNavigator(this)
}
override fun getBindingVariable(): Int {
return BR.viewModel
}
override fun getLayoutId(): Int {
return R.layout.activity_main
}
override fun getViewModel(): MainViewModel<MainNavigator>? {
mViewModel =
ViewModelProviders.of(this, factory).get<MainViewModel<MainNavigator>>(MainViewModel<MainNavigator>::class.java)
return mViewModel
}
/*
#Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return fragmentDispatchingAndroidInjector;
}
*/
companion object {
private const val TAG = "MainActivity"
}
}
ViewModelProviderFactory
package com.logicipher.mvvm
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider.NewInstanceFactory
import com.logicipher.mvvm.data.DataManager
import com.logicipher.mvvm.ui.main.MainNavigator
import com.logicipher.mvvm.ui.main.MainViewModel
import com.logicipher.mvvm.utils.rx.SchedulerProvider
import java.lang.IllegalArgumentException
import javax.inject.Inject
/**
* Created by Shamji N S on 25-08-2020.
*/
public class ViewModelProviderFactory #Inject constructor(
dataManager: DataManager,
schedulerProvider: SchedulerProvider
) : NewInstanceFactory() {
private val dataManager: DataManager
private val schedulerProvider: SchedulerProvider
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return super.create(modelClass)
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel<T>(dataManager, schedulerProvider) as T
}
throw IllegalArgumentException("Unknown view model" + modelClass.name)
}
init {
this.dataManager = dataManager
this.schedulerProvider = schedulerProvider
}
}
Your BaseViewModel is abstract, you cannot create an object or an instance of an abstract class.
After you create a ViewModelFactory the way to create (or obtain) an instance of the ViewModel is this:
val viewModel = ViewModelProvider(this, viewModelFactory).get(ViewModelName::class.java)
However, I would consider using Kotlin Property Delegate from here which can help reduce the code.
Just after calling loadInitial method it automatically starts calling loadAfter method until and unless all pages are loaded and it gets an empty array from response. I am attaching my DataSource and DataSourceFactory code along with ViewModel function and PagedListAdapter.
PassbookDataSource.kt
package co.indiagold.gold.buy.loan.home
import androidx.paging.PageKeyedDataSource
import co.indiagold.gold.buy.loan.helper.GenericMethods.handleServerError
import co.indiagold.gold.buy.loan.helper.GenericResponse
import co.indiagold.gold.buy.loan.network.Repository
import co.indiagold.gold.buy.loan.network.ResultWrapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
class PassbookDataSource(private val accessToken: String, private val assetName:String,private val viewModelScope: CoroutineScope) : PageKeyedDataSource<Int, PassbookResponseModel>() {
private val PAGE_SIZE = 20
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, PassbookResponseModel>) {
var result: ResultWrapper<GenericResponse<ArrayList<PassbookResponseModel>?>?>? = null
viewModelScope.launch {
result = Repository.getPassbookDetail(accessToken, assetName,1, PAGE_SIZE)
}.invokeOnCompletion {
if (result is ResultWrapper.Success) {
val successResult = result as ResultWrapper.Success
callback.onResult(successResult.value!!.result as MutableList<PassbookResponseModel>, null, 2)
} else {
it.handleServerError("vault/passbook")
}
}
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, PassbookResponseModel>) {
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, PassbookResponseModel>) {
var result: ResultWrapper<GenericResponse<ArrayList<PassbookResponseModel>?>?>? = null
viewModelScope.launch {
result = Repository.getPassbookDetail(accessToken,assetName, params.key, PAGE_SIZE)
}.invokeOnCompletion {
if (result is ResultWrapper.Success) {
val successResult = result as ResultWrapper.Success
callback.onResult(successResult.value!!.result as MutableList<PassbookResponseModel>, params.key+1)
} else {
it.handleServerError("vault/passbook")
}
}
}
}
PassbookDataSourceFactory.kt
class PassbookDataSourceFactory(private val accessToken:String,private val assetName:String,private val viewModelScope:CoroutineScope) : DataSource.Factory<Int,PassbookResponseModel>(){
val orderListMLD = MutableLiveData<PassbookDataSource>(null)
var orderDataSource : PassbookDataSource? = null
override fun create(): DataSource<Int, PassbookResponseModel> {
orderDataSource = PassbookDataSource(accessToken,assetName,viewModelScope)
orderListMLD.postValue(orderDataSource)
return orderDataSource!!
}
}
Viewmodel Function
fun getPassbookDetail(assetName: String) {
val accessToken = GenericMethods.getDataFromPreferences(Constants.SHARED_PREFS.LOGIN_PREFERENCES, Constants.SHARED_PREFS.ACCESS_TOKEN)
if (accessToken.isNullOrEmpty()) {
handleNullAccessTokenCase()
} else {
val factory = PassbookDataSourceFactory(accessToken, assetName, viewModelScope)
val config = PagedList.Config.Builder()
.setEnablePlaceholders(true)
.setInitialLoadSizeHint(10)
.setPrefetchDistance(4)
.build()
val executor = Executors.newFixedThreadPool(5)
passbookResponseMLD = LivePagedListBuilder(factory, config)
.setFetchExecutor(executor)
.build()
}
}
PassbookAdapter.kt
package co.indiagold.gold.buy.loan.home
import android.app.Activity
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.paging.PagedList
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import co.indiagold.gold.buy.loan.databinding.CardPassbookBinding
import co.indiagold.gold.buy.loan.helper.Constants
import co.indiagold.gold.buy.loan.orders.OrdersActivity
import kotlinx.android.synthetic.main.card_passbook.view.*
class PassbookAdapter(private val activity: Activity,private val emptyListListener: EmptyListListener) : PagedListAdapter<PassbookResponseModel,PassbookAdapter.MyHolder>(PASSBOOK_DIFF_CALLBACK) {
private lateinit var binding: CardPassbookBinding
class MyHolder(itemView : View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder {
binding = CardPassbookBinding.inflate(LayoutInflater.from(parent.context))
emptyListListener.onListLoad(currentList?.size)
return MyHolder(binding.root)
}
override fun onCurrentListChanged(previousList: PagedList<PassbookResponseModel>?, currentList: PagedList<PassbookResponseModel>?) {
super.onCurrentListChanged(previousList, currentList)
emptyListListener.onListLoad(currentList?.size)
}
override fun onBindViewHolder(holder: MyHolder, position: Int) {
binding.passbookEntry = getItem(position)
holder.itemView.passbookRootLayout.setOnClickListener {
val intent = Intent(activity,OrdersActivity::class.java)
intent.putExtra(Constants.BUNDLE_STRINGS.ORDER_ID,getItem(position)?.referenceId)
activity.startActivity(intent)
}
}
override fun getItemId(position: Int): Long {
return getItem(position)!!.createdAt
}
/*override fun getItemViewType(position: Int): Int {
return position
}*/
}
private val PASSBOOK_DIFF_CALLBACK = object : DiffUtil.ItemCallback<PassbookResponseModel>(){
override fun areItemsTheSame(oldItem: PassbookResponseModel, newItem: PassbookResponseModel): Boolean {
return oldItem.createdAt == newItem.createdAt
}
override fun areContentsTheSame(oldItem: PassbookResponseModel, newItem: PassbookResponseModel): Boolean {
return oldItem == newItem
}
}
The issue is, that you may put recycler view inside NextedScrollview or ScrollView, by removing scrollview it will work fine
here is my BaseActivity.java, i converted it from java to kotlin:
package me.wcy.music.activity
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.media.AudioManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import me.wcy.music.R
import me.wcy.music.application.AppCache
import me.wcy.music.service.PlayService
import me.wcy.music.utils.binding.ViewBinder
import me.wcy.music.utils.permission.PermissionReq
/**
* 基类<br></br>
* 如果继承本类,需要在 layout 中添加 [Toolbar] ,并将 AppTheme 继承 Theme.AppCompat.NoActionBar 。
* Created by wcy on 2015/11/26.
*/
abstract class BaseActivity : AppCompatActivity() {
protected var mHandler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
try{
super.onCreate(savedInstanceState)
}catch (e:Exception){
e.printStackTrace()
}
setSystemBarTransparent()
volumeControlStream = AudioManager.STREAM_MUSIC
}
override fun setContentView(layoutResID: Int) {
super.setContentView(layoutResID)
initView()
}
override fun setContentView(view: View) {
super.setContentView(view)
initView()
}
override fun setContentView(view: View, params: ViewGroup.LayoutParams) {
super.setContentView(view, params)
initView()
}
private fun initView() {
ViewBinder.bind(this)
val mToolbar = findViewById(R.id.toolbar) as Toolbar ?: throw IllegalStateException("Layout is required to include a Toolbar with id 'toolbar'")
setSupportActionBar(mToolbar)
if (supportActionBar != null) {
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
}
}
override fun onStart() {
super.onStart()
setListener()
}
protected open fun setListener() {}
val playService: PlayService
get() {
val playService = AppCache.playService ?: throw NullPointerException("play service is null")
return playService
}
protected fun checkServiceAlive(): Boolean {
if (AppCache.playService == null) {
startActivity(Intent(this, SplashActivity::class.java))
AppCache.clearStack()
return false
}
return true
}
private fun setSystemBarTransparent() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// LOLLIPOP解决方案
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.statusBarColor = Color.TRANSPARENT
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// KITKAT解决方案
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
return true
}
return super.onOptionsItemSelected(item)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
PermissionReq.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
fun showSoftKeyboard(editText: EditText) {
editText.isFocusable = true
editText.isFocusableInTouchMode = true
editText.requestFocus()
mHandler.postDelayed({
val inputManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputManager.showSoftInput(editText, 0)
}, 200L)
}
fun hideSoftKeyboard() {
if (currentFocus != null) {
val inputManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputManager.hideSoftInputFromWindow(currentFocus!!.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
}
}
}
i've already add "a" ? in onCreate(saveInstanceState:Bundle)
but still got the IllegalArgumentException :Parameter specified as non-null is null:method kotlin jvm.internal.Intrinsics.checkParameterIsNotNull,parameter savesInstanceState
here's my log
W/System: ClassLoader referenced unknown path: /data/app/me.wcy.music-1/lib/arm64
W/art: Before Android 4.1, method android.graphics.PorterDuffColorFilter android.support.graphics.drawable.VectorDrawableCompat.updateTintFilter(android.graphics.PorterDuffColorFilter, android.content.res.ColorStateList, android.graphics.PorterDuff$Mode) would have incorrectly overridden the package-private method in android.graphics.drawable.Drawable
W/System.err: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter savedInstanceState
W/System.err: at me.wcy.music.application.AppCache$ActivityLifecycle.onActivityCreated(AppCache.kt)
W/System.err: at android.app.Application.dispatchActivityCreated(Application.java:195)
W/System.err: at android.app.Activity.onCreate(Activity.java:956)
W/System.err: at android.support.v4.app.BaseFragmentActivityGingerbread.onCreate(BaseFragmentActivityGingerbread.java:54)
W/System.err: at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:319)
W/System.err: at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:85)
W/System.err: at me.wcy.music.activity.BaseActivity.onCreate(BaseActivity.kt:36)
W/System.err: at me.wcy.music.activity.SplashActivity.onCreate(SplashActivity.kt:42)
W/System.err: at android.app.Activity.performCreate(Activity.java:6372)
W/System.err: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1110)
W/System.err: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2432)
W/System.err: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2539)
W/System.err: at android.app.ActivityThread.access$900(ActivityThread.java:168)
W/System.err: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1378)
W/System.err: at android.os.Handler.dispatchMessage(Handler.java:102)
W/System.err: at android.os.Looper.loop(Looper.java:150)
W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5665)
W/System.err: at java.lang.reflect.Method.invoke(Native Method)
W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:822)
W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:712)
here is the SplashActivity:
package me.wcy.music.activity
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.AsyncTask
import android.os.Bundle
import android.os.IBinder
import android.text.TextUtils
import android.widget.ImageView
import android.widget.TextView
import java.io.File
import java.util.Calendar
import me.wcy.music.R
import me.wcy.music.application.AppCache
import me.wcy.music.http.HttpCallback
import me.wcy.music.http.HttpClient
import me.wcy.music.model.Splash
import me.wcy.music.service.PlayService
import me.wcy.music.utils.FileUtils
import me.wcy.music.utils.Preferences
import me.wcy.music.utils.ToastUtils
import me.wcy.music.utils.binding.Bind
import me.wcy.music.utils.permission.PermissionReq
import me.wcy.music.utils.permission.PermissionResult
import me.wcy.music.utils.permission.Permissions
class SplashActivity : BaseActivity() {
#Bind(R.id.iv_splash)
private val ivSplash: ImageView? = null
#Bind(R.id.tv_copyright)
private val tvCopyright: TextView? = null
private var mPlayServiceConnection: ServiceConnection? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
val year = Calendar.getInstance().get(Calendar.YEAR)
tvCopyright!!.text = getString(R.string.copyright, year)
checkService()
}
private fun checkService() {
if (AppCache.playService == null) {
startService()
showSplash()
updateSplash()
mHandler.postDelayed({ bindService() }, 1000)
} else {
startMusicActivity()
finish()
}
}
private fun startService() {
val intent = Intent(this, PlayService::class.java)
startService(intent)
}
private fun bindService() {
val intent = Intent()
intent.setClass(this, PlayService::class.java)
mPlayServiceConnection = PlayServiceConnection()
bindService(intent, mPlayServiceConnection, Context.BIND_AUTO_CREATE)
}
private inner class PlayServiceConnection : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
val playService = (service as PlayService.PlayBinder).service
AppCache.playService = playService
PermissionReq.with(this#SplashActivity)
.permissions(*Permissions.STORAGE)
.result(object : PermissionResult {
override fun onGranted() {
scanMusic(playService)
}
override fun onDenied() {
ToastUtils.show(getString(R.string.no_permission, Permissions.STORAGE_DESC, "扫描本地歌曲"))
finish()
playService.stop()
}
})
.request()
}
override fun onServiceDisconnected(name: ComponentName) {}
}
private fun scanMusic(playService: PlayService) {
object : AsyncTask<Void, Void, Void>() {
override fun doInBackground(vararg params: Void): Void? {
playService.updateMusicList()
return null
}
override fun onPostExecute(aVoid: Void) {
startMusicActivity()
finish()
}
}.execute()
}
private fun showSplash() {
val splashImg = File(FileUtils.getSplashDir(this), SPLASH_FILE_NAME)
if (splashImg.exists()) {
val bitmap = BitmapFactory.decodeFile(splashImg.path)
ivSplash!!.setImageBitmap(bitmap)
}
}
private fun updateSplash() {
HttpClient.getSplash(object : HttpCallback<Splash>() {
override fun onSuccess(response: Splash?) {
if (response == null || TextUtils.isEmpty(response.url)) {
return
}
val url = response.url
val lastImgUrl = Preferences.splashUrl
if (TextUtils.equals(lastImgUrl, url)) {
return
}
HttpClient.downloadFile(url, FileUtils.getSplashDir(AppCache.context), SPLASH_FILE_NAME,
object : HttpCallback<File>() {
override fun onSuccess(file: File?) {
Preferences.saveSplashUrl(url)
}
override fun onFail(e: Exception?) {}
})
}
override fun onFail(e: Exception?) {}
})
}
private fun startMusicActivity() {
val intent = Intent()
intent.setClass(this, MusicActivity::class.java)
intent.putExtras(getIntent())
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
}
override fun onBackPressed() {}
override fun onDestroy() {
if (mPlayServiceConnection != null) {
unbindService(mPlayServiceConnection)
}
super.onDestroy()
}
companion object {
private val SPLASH_FILE_NAME = "splash"
}
}