How to call baseactivity function from viewmodel in android - android

Hi I have LoginActivity and LoginViewModel and some more classes. I have showLoading and hideLoading in the BaseActivity so it can be accessible from each activity.
I am able to call LoginActivity method from the LoginViewModel like mNavigator?.startForgotPasswordActivity()
I want to call it from the LoginViewModel then what the way to do it using MVVM ? or I am going with wrong approach. Please suggest what is the correct way to do this ?
BaseActivity.kt
abstract class BaseActivity : AppCompatActivity(), AnkoLogger {
private val progressBar: ProgressBar? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
protected fun getToolbar(): Toolbar {
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
return toolbar
}
protected 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)
}
}
#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 isNetworkConnected(): Boolean {
return NetworkUtils.isNetworkConnected(applicationContext)
}
fun showLoading() {
hideLoading()
// show progress bar
}
fun hideLoading() {
// hide progress bar
}
}
LoginActivity.kt
class LoginActivity : BaseActivity(), LoginNavigator {
#Inject
lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
performDependencyInjection()
super.onCreate(savedInstanceState)
val activityLoginBinding: ActivityLoginBinding = DataBindingUtil.setContentView<ActivityLoginBinding>(this, R.layout.activity_login)
activityLoginBinding.loginViewModel = loginViewModel
loginViewModel.mNavigator = this
}
override fun startHomeActivity() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun startRegistrationActivity() {
startActivity(Intent(this, RegistrationActivity::class.java))
}
override fun startForgotPasswordActivity() {
startActivity(Intent(this, ForgotPasswordActivity::class.java))
}
override fun handleError(throwable: Throwable) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
LoginViewModel.kt
class LoginViewModel : BaseViewModel<LoginNavigator>(), AnkoLogger {
val emailField = ObservableField<String>()
private val email: String
get() = emailField.get()
val passwordField = ObservableField<String>()
private val password: String
get() = passwordField.get()
#Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
fun login(view: View) {
if (isEmailAndPasswordValid(email, password)) {
ApiHelperImpl().doServerLoginApiCall(email, password)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : CallbackWrapper<LoginResponse>() {
override fun onSuccess(loginResponse: LoginResponse) {
info { loginResponse }
}
})
}
}
/**
* Validate email and password. It checks email and password is empty or not
* and validate email address is correct or not
* #param email email address for login
* #param password password for login
* #return true if email and password pass all conditions else false
*/
private fun isEmailAndPasswordValid(email: String, password: String): Boolean {
if (email.isEmpty()) return false
if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) return false
if (password.isEmpty()) return false
return true
}
}
BaseViewModel.kt
abstract class BaseViewModel<N> {
var mNavigator: N? = null
}

There can be 2 approach for same
1) Use all functions i.e related to UI update or UI event listener from a view (Activity or Fragment) according to mvp and from viewmodel only try to manage data like api's and other logic
class LoginActivity : BaseActivity(), LoginNavigator {
#Inject
lateinit var mLoginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
mLoginViewModel.mNavigator = this
callApi()
}
private fun callApi() {
showLoading()
mLoginViewModel.callApi()
}
override fun openHomeScreen(model: Model) {
hideLoading()
showSnackBar(constraint_root, model.Domain)
}
}
class LoginViewModel(sessionManager: SessionManager, requestInterface: RequestInterface) : BaseViewModel<LoginNavigator>(sessionManager, requestInterface) {
fun callApi() {
requestInterface.getServiceIP()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleResponse, this::handleError)
}
private fun handleResponse(model: Model) {
if (model.Domain == null) {
mNavigator!!.openHomeScreen(model)
} else {
}
}
private fun handleError(error: Throwable) {
error.printStackTrace()
}
}
2)
In Login interface add a function
interface LoginNavigator {
fun openHomeScreen()
fun getActivity(): BaseActivity
}
In LoginActivity override the function and return
override fun getActivity(): BaseActivity = this
Now using navigator you can access base activity & call show/hide loader function
mNavigator!!.getActivity().showLoading()

Related

MutableSharedFlow is being re collected again and again

I have the following ViewModel and Fragment -
class HeroesViewModel(private val heroesRepository: HeroesRepository) : ViewModel() {
private val internalUiState = MutableStateFlow<UiState>(UiState.Initial)
val uiState = internalUiState.asLiveData()
private val internalUiAction = MutableSharedFlow<UiAction>(1).apply {
tryEmit(UiAction.GetSuggestedList)
}
val uiAction = internalUiAction.asLiveData()
private val externalUiEvent = MutableSharedFlow<UiEvent>(1)
private val uiEvent = externalUiEvent.asSharedFlow()
init {
observeUiEvents()
}
private fun observeUiEvents() = viewModelScope.launch {
uiEvent.collect { event ->
when (event) {
is UiEvent.ListItemClicked -> {
navigateToHeroDetails(event.heroModel)
}
is UiEvent.SearchTextChanged -> {
getHeroesByName(event.searchText)
}
}
}
}
private fun navigateToHeroDetails(heroModel: HeroesListModel) =
submitAction(UiAction.NavigateToHeroesDetails(heroModel))
private fun getHeroesByName(name: String) = viewModelScope.launch(Dispatchers.IO) {
when (val response = heroesRepository.getHeroesByNameWithSuggestions(name)) {
is NetworkResponse.Success -> {
internalUiState.emit(UiState.Data(response.body as List<HeroesListModel>))
}
is NetworkResponse.Error -> {
response.error.message?.let { message ->
internalUiState.emit(UiState.Error(message))
}
}
else -> {}
}
}
fun getSuggestedHeroesList() = viewModelScope.launch(Dispatchers.IO) {
when (val response = heroesRepository.getSuggestedHeroesList(true)) {
is NetworkResponse.Success -> {
submitState(UiState.Data(response.body as List<HeroesListModel>))
}
is NetworkResponse.Error -> {
response.error.message?.let { message ->
submitState(UiState.Error(message))
}
}
else -> {}
}
}
private fun submitAction(uiAction: UiAction) = internalUiAction.tryEmit(uiAction)
private fun submitState(uiState: UiState) = viewModelScope.launch {
internalUiState.emit(uiState)
}
fun submitEvent(uiEvent: UiEvent) = externalUiEvent.tryEmit(uiEvent)
sealed class UiEvent {
data class SearchTextChanged(val searchText: String) : UiEvent()
data class ListItemClicked(val heroModel: HeroesListModel) : UiEvent()
}
sealed class UiState {
data class Data(val modelsListResponse: List<BaseHeroListModel>) : UiState()
data class Error(val errorMessage: String) : UiState()
object Initial : UiState()
}
sealed class UiAction {
data class NavigateToHeroesDetails(val heroModel: HeroesListModel) : UiAction()
object GetSuggestedList : UiAction()
}
}
class DashboardFragment : Fragment() {
//Class Variables - UI
private lateinit var binding: FragmentDashboardBinding
//Class Variables - Dependency Injection
private val heroesViewModel = get<HeroesViewModel>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentDashboardBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
init()
observeUiState()
observeUiAction()
}
private fun observeUiAction() = heroesViewModel.uiAction.observe(viewLifecycleOwner) { action ->
when(action){
is HeroesViewModel.UiAction.GetSuggestedList -> {
getSuggestedHeroesList()
}
is HeroesViewModel.UiAction.NavigateToHeroesDetails -> {
navigateToHeroesDetails(action.heroModel)
}
}
}
private fun init() {
binding.heroesSearchView.setOnQueryTextListener(object : OnSearchViewOnlyTextChangedListener() {
override fun onQueryTextChange(newText: String?): Boolean {
if (newText.isNullOrEmpty()) return false
heroesViewModel.submitEvent(HeroesViewModel.UiEvent.SearchTextChanged(newText))
binding.progressBar.setVisiblyAsVisible()
return false
}
})
}
private fun observeUiState() = heroesViewModel.uiState.observe(viewLifecycleOwner) { uiAction ->
when (uiAction) {
is HeroesViewModel.UiState.Data -> {
showHeroesList(uiAction)
}
is HeroesViewModel.UiState.Error -> {
showGeneralError(uiAction)
}
HeroesViewModel.UiState.Initial -> Unit
}
}
private fun navigateToHeroesDetails(heroModel: HeroesListModel) =
findNavController().navigate(DashboardFragmentDirections.actionMainFragmentToHeroesDetailsFragment(heroModel))
private fun showHeroesList(result: HeroesViewModel.UiState.Data) {
binding.heroesList.setContent {
LazyColumn {
items(result.modelsListResponse.toList()) { model ->
if (model is HeroListSeparatorModel)
HeroesListSeparatorItem(model)
else if (model is HeroesListModel)
HeroesListItem(model) {
heroesViewModel.submitEvent(HeroesViewModel.UiEvent.ListItemClicked(model))
}
}
}
}
binding.progressBar.setVisiblyAsGone()
}
private fun showGeneralError(result: HeroesViewModel.UiState.Error) {
Toast.makeText(requireContext(), result.errorMessage, Toast.LENGTH_LONG).show()
binding.progressBar.setVisiblyAsGone()
}
private fun getSuggestedHeroesList() {
heroesViewModel.getSuggestedHeroesList()
binding.progressBar.setVisiblyAsVisible()
}
}
As you can see, I have the replayCache set to 1 in internalUiAction but the value keeps emitting itself. When I navigate using the navigateToHeroesDetails() method and go back using the navigation bar I immediately observe the last uiAction emitted value which is NavigateToHeroesDetails, causing me to navigate again and again to the heroes details screen. This is an endless loop of navigation.
As far as a hint for a solution, if I double tap the navigation 2 times quickly it does indeed go back to the first Fragment. Seems like I am missing something related to SharedFlow

Retrofit or Okhttp never called the link

So I got a problem, When the Main Page is shown, it's supposed to call the API to get the data from it. But, the API never called. What I find strange is, that before Main Page, there is a login page that successfully called the API for login. But, on Main Page, the Retrofit/Okhttp never called the Page to get the data. Do you guys know what's wrong?
Here's my code screenshot:
StoryPageSource
StoryRepository
ViewModel
StoryAdapter
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var bind: ActivityMainBinding
private lateinit var storyAdapter: StoryAdapter
private val timelineStoryViewModel: TimelineStoryViewModel by viewModels {
ViewModelFactory(this, application)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bind = ActivityMainBinding.inflate(layoutInflater)
setContentView(bind.root)
showLoadingProcess(true)
displayStories()
bind.floatingActionButton.setOnClickListener {
startActivity(Intent(this, AddStoryActivity::class.java))
}
supportActionBar?.setTitle(R.string.app_name)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflaterMenu = menuInflater
inflaterMenu.inflate(R.menu.menus, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.app_bar_settings -> {
val context = this
val pref = context.getSharedPreferences(
R.string.token_pref.toString(),
Context.MODE_PRIVATE
)
val editor = pref.edit()
editor.remove(R.string.token.toString())
editor.remove(getString(R.string.email))
editor.remove(getString(R.string.password))
editor.apply()
startActivity(Intent(this, LoginActivity::class.java))
}
R.id.app_bar_maps -> {
startActivity(Intent(this, MapsActivity::class.java))
}
}
return super.onOptionsItemSelected(item)
}
private fun displayStories() {
showLoadingProcess(false)
storyAdapter = StoryAdapter()
bind.storyRV.layoutManager = LinearLayoutManager(this)
bind.storyRV.adapter = storyAdapter
timelineStoryViewModel.story().observe(this#MainActivity) { story ->
storyAdapter.submitData(lifecycle, story)
}
storyAdapter.setOnProfileCallback(object : StoryAdapter.OnProfileCallback {
override fun onProfileClicked(data: TimelineStory) {
chosenProfile(data)
}
})
}
private fun chosenProfile(story: TimelineStory) {
var name: TextView = findViewById(R.id.userName)
var storyPict: ImageView = findViewById(R.id.userImageStory)
val optionsCompat: ActivityOptionsCompat =
ActivityOptionsCompat.makeSceneTransitionAnimation(
this,
Pair(storyPict, getString(R.string.imageStoryDetail)),
Pair(name, getString(R.string.nama_pengguna)),
)
val sendData = Intent(this, StoryDetailActivity::class.java)
sendData.putExtra(StoryDetailActivity.EXTRA_STORY, story)
startActivity(sendData, optionsCompat.toBundle())
Toast.makeText(this, "Memuat Story " + story.name, Toast.LENGTH_SHORT).show()
}
private fun showLoadingProcess(isLoading: Boolean) {
if (isLoading) {
bind.loading.visibility = View.VISIBLE
} else {
bind.loading.visibility = View.GONE
}
}
}

Handle search results after configuration change MVVM

I am building a movie app based on "The Movie DB". My app has only one activity with a recyclerview and one search action button (SearchView).
As soon as the app starts,the recyclerview filled with popularMovies list.
When I search for a movie I get the results and everything is fine,but when I rotate the screen I get the popularMovies list and not my search results.
Now I know the reason,its happening because in onCreate I have initMoviesRecyclerView() that fill the recyclerview with popularMovies.
My question is how can I keep the search results without filling the recyclerview with popularMovies ? How can I do this the correct way ?
This is the Repository Class:
class MainRepository {
//MutableLiveData
private val popularMoviesMutableLiveData = MutableLiveData<List<Movie>>()
private val searchAfterMutableLiveData = MutableLiveData<List<Movie>>()
// API
private val apiService : GetFromApi = APIService.retrofitClientRequest
private val apiKey = "NOT"
fun getPopularMoviesList() : MutableLiveData<List<Movie>>{
apiService.getPopularMovies(apiKey,1)?.enqueue(object : Callback<MovieListResult?> {
override fun onResponse(
call: Call<MovieListResult?>,
response: Response<MovieListResult?>
) {
if (response.isSuccessful){
popularMoviesMutableLiveData.value = response.body()?.moviesResults
Log.e("MovieListResults","Result: ${popularMoviesMutableLiveData.value}")
}
}
override fun onFailure(call: Call<MovieListResult?>, t: Throwable) {
Log.e("MovieListResults","Failed: ${t.message}")
}
})
return popularMoviesMutableLiveData
}
fun searchAfter(searchAfter : String) : MutableLiveData<List<Movie>>{
apiService.searchAfter(apiKey,searchAfter)?.enqueue(object : Callback<MovieListResult?> {
override fun onResponse(
call: Call<MovieListResult?>,
response: Response<MovieListResult?>
) {
if (response.isSuccessful){
searchAfterMutableLiveData.value = response.body()?.moviesResults
Log.e("SearchMovieListResults","Result: ${searchAfterMutableLiveData.value}")
}
}
override fun onFailure(call: Call<MovieListResult?>, t: Throwable) {
Log.e("SearchMovieListResults","Failed: ${t.message}")
}
})
return searchAfterMutableLiveData
}
}
This is the viewModel class:
class MainViewModel : ViewModel(){
//Repository
private val mainRepository = MainRepository()
//MutableLiveData
var popularMoviesMutableLiveData = MutableLiveData<List<Movie>>()
var searchAfterMutableLiveData = MutableLiveData<List<Movie>>()
//The Main Movie List
var mainMovieList = listOf<Movie>()
fun getPopularMovies() : LiveData<List<Movie>>{
popularMoviesMutableLiveData = mainRepository.getPopularMoviesList()
popularMoviesMutableLiveData.value = mainMovieList
return popularMoviesMutableLiveData
}
fun getMovieBySearch(searchAfter : String) : LiveData<List<Movie>>{
searchAfterMutableLiveData = mainRepository.searchAfter(searchAfter)
searchAfterMutableLiveData.value = mainMovieList
return searchAfterMutableLiveData
}
}
This is the MainActivity class:
class MainActivity : AppCompatActivity() {
//ViewModel
private val mainViewModel : MainViewModel by viewModels()
// Views
private lateinit var mainRecyclerView : RecyclerView
private lateinit var mainAdapter : MainRecyclerViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initMoviesRecyclerView()
}
private fun initMoviesRecyclerView() {
mainRecyclerView = findViewById(R.id.mainRecyclerView)
mainRecyclerView.setHasFixedSize(true)
mainRecyclerView.layoutManager = GridLayoutManager(this,1)
mainViewModel.getPopularMovies().observe(this, object : Observer<List<Movie>?> {
override fun onChanged(newList: List<Movie>?) {
if (newList != null) {
mainViewModel.mainMovieList = newList
mainAdapter = MainRecyclerViewAdapter(mainViewModel.mainMovieList)
mainRecyclerView.adapter = mainAdapter
}
}
})
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main_menu,menu)
val searchView = menu.findItem(R.id.menu_search_movie).actionView as androidx.appcompat.widget.SearchView
searchView.queryHint = "Search By Name,Actor .."
searchView.setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(whileTextChange: String?): Boolean {
//Clear SearchView
searchView.isIconified = true
searchView.setQuery("", false)
searchView.onActionViewCollapsed()
mainViewModel.getMovieBySearch(whileTextChange.toString()).observe(this#MainActivity,object : Observer<List<Movie>?> {
override fun onChanged(newList: List<Movie>?) {
if (newList != null) {
mainViewModel.mainMovieList = newList
mainAdapter.changeCurrentList(mainViewModel.mainMovieList)
}
}
})
return false
}
override fun onQueryTextChange(whileTextChange: String?): Boolean {
Log.e("onQueryTextChange","Text: $whileTextChange")
return false
}
})
return true
}
}
Thank you!
I don't see where you actually populate the data inside the ViewModel. But it all looked good.
This line is a bit suspicious:
mainViewModel.mainMovieList = newList
You should not update the ViewModel from the View lifeCycle. Also, you should not update the ViewModel during the observe call.
So after sometime I manage to work around it with a simple solution:
I created a function in the viewModel,to check if the search list value is null:
fun startFromSearch(): Boolean {
return searchAfterMutableLiveData.value != null
}
After that I made an if statement in the initMoviesRecyclerView(), and that's it!
private fun initMoviesRecyclerView() {
mainRecyclerView = findViewById(R.id.mainRecyclerView)
mainRecyclerView.setHasFixedSize(true)
mainRecyclerView.layoutManager = GridLayoutManager(this,1)
mainAdapter = MainRecyclerViewAdapter(mainViewModel.mainMovieList,this)
mainRecyclerView.adapter = mainAdapter
if (!mainViewModel.startFromSearch()){
mainViewModel.getPopularMovies(1).observe(this, object : Observer<List<Movie>?> {
override fun onChanged(newList: List<Movie>?) {
if (newList != null) {
mainAdapter = MainRecyclerViewAdapter(newList,this#MainActivity)
mainRecyclerView.adapter = mainAdapter
}
}
})
}
}

delay init methods in ViewModel

I have following project in Github : https://github.com/AliRezaeiii/TMDb-Paging
I have to postDelay calling methods in my ViewModel since datasource is not initialized :
abstract class DetailViewModel(private val item: TmdbItem) : BaseViewModel() {
private val handler = Handler(Looper.getMainLooper())
val trailers: ObservableList<Video> = ObservableArrayList()
val isTrailersVisible = ObservableBoolean(false)
private val _cast = MutableLiveData<List<Cast>>()
val cast: LiveData<List<Cast>> = _cast
val isCastVisible = ObservableBoolean(false)
init {
handler.postDelayed({
showTrailers()
showCast()
}, 100)
}
protected abstract fun getTrailers(id: Int): Observable<List<Video>>
protected abstract fun getCast(id: Int): Observable<List<Cast>>
private fun showTrailers() {
EspressoIdlingResource.increment() // App is busy until further notice
compositeDisposable.add(getTrailers(item.id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally {
if (!EspressoIdlingResource.getIdlingResource().isIdleNow) {
EspressoIdlingResource.decrement() // Set app as idle.
}
}
.subscribe({ videos ->
if (videos.isNotEmpty()) {
isTrailersVisible.set(true)
}
with(trailers) {
clear()
addAll(videos)
}
}
) { throwable -> Timber.e(throwable) })
}
private fun showCast() {
EspressoIdlingResource.increment() // App is busy until further notice
compositeDisposable.add(getCast(item.id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally {
if (!EspressoIdlingResource.getIdlingResource().isIdleNow) {
EspressoIdlingResource.decrement() // Set app as idle.
}
}
.subscribe({ cast ->
if (cast.isNotEmpty()) {
isCastVisible.set(true)
}
this._cast.postValue(cast)
}
) { throwable -> Timber.e(throwable) })
}
}
And here is my Fragment :
abstract class DetailFragment<T : TmdbItem>
: BaseDaggerFragment(), CastClickCallback {
protected abstract fun getViewModel(): DetailViewModel
protected abstract fun getLayoutId(): Int
protected abstract fun initViewBinding(root: View): ViewDataBinding
protected abstract fun getTmdbItem(): T
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val viewModel = getViewModel()
val root = inflater.inflate(getLayoutId(), container, false)
initViewBinding(root).apply {
setVariable(BR.vm, viewModel)
lifecycleOwner = viewLifecycleOwner
}
with(root) {
with(activity as AppCompatActivity) {
setupActionBar(details_toolbar) {
setDisplayShowTitleEnabled(false)
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
}
summary_label.visibleGone(getTmdbItem().overview.trim().isNotEmpty())
// Make the MotionLayout draw behind the status bar
details_motion.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
summary.setOnClickListener {
val maxLine = resources.getInteger(R.integer.max_lines)
summary.maxLines = if (summary.maxLines > maxLine) maxLine else Int.MAX_VALUE
}
viewModel.cast.observe(viewLifecycleOwner, Observer {
it?.apply {
val adapter = CastAdapter(it, this#DetailFragment)
cast_list.apply {
setHasFixedSize(true)
cast_list.adapter = adapter
}
}
})
with(details_rv) {
postDelayed({ scrollTo(0, 0) }, 100)
}
}
return root
}
}
And BaseDaggerFragment :
open class BaseDaggerFragment : DaggerFragment() {
#Inject
lateinit var dataSource: RemoteDataSource
}
Could be any better solution than :
init {
handler.postDelayed({
showTrailers()
showCast()
}, 100)
}
You can lazy initialize like this way
private val users:MutableLiveData<List<Cast>> by lazy {
MutableLiveData().also {
showTrailers()
showCast()
}
}
more details refer ViewModel

Getting my location LatLng from Mapbox in kotlin

I'm trying to get my location using mapbox on an android application built with kotlin. I'm using the locationComponent method to get it, here is my code :
class PlaceholderFragment : Fragment(), OnMapReadyCallback, PermissionsListener {
private var permissionsManager: PermissionsManager = PermissionsManager(this)
private var mapboxMap: MapboxMap? = null
private var myCurrentLocation: LatLng? = null
var navigationMapRoute: NavigationMapRoute? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pageViewModel = ViewModelProviders.of(this).get(PageViewModel::class.java).apply {
setIndex(arguments?.getInt(ARG_SECTION_NUMBER) ?: 1)
}
val theActivity = activity as NavigationActivity?
theSteps = theActivity?.theSteps
case = theActivity?.case
case = theActivity.case
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
activity?.let {
Mapbox.getInstance(
it,
getString(com.innoventiq.arkbeh.R.string.mapbox_access_token2)
)
}
val root =
inflater.inflate(com.innoventiq.arkbeh.R.layout.fragment_navigation, container, false)
return root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync(this)
getMyLocation()
}
override fun onExplanationNeeded(permissionsToExplain: MutableList<String>?) {
Toast.makeText(
activity,
com.innoventiq.arkbeh.R.string.user_location_permission_explanation,
Toast.LENGTH_LONG
)
.show()
}
override fun onPermissionResult(granted: Boolean) {
if (granted) {
enableLocationComponent(mapboxMap?.style!!)
} else {
Toast.makeText(
activity,
com.innoventiq.arkbeh.R.string.user_location_permission_not_granted,
Toast.LENGTH_LONG
)
.show()
}
}
override fun onMapReady(mapboxMap: MapboxMap) {
this.mapboxMap = mapboxMap
mapboxMap.cameraPosition = CameraPosition.Builder()
.target(myCurrentLocation)
.zoom(14.0)
.build()
//mapView.setOnTouchListener { v, event -> true }
mapboxMap.setStyle(Style.OUTDOORS) {
enableLocationComponent(it)
}
}
#SuppressLint("MissingPermission")
private fun enableLocationComponent(loadedMapStyle: Style) {
// Check if permissions are enabled and if not request
if (PermissionsManager.areLocationPermissionsGranted(activity)) {
// Create and customize the LocationComponent's options
val customLocationComponentOptions = activity?.let {
LocationComponentOptions.builder(it)
.trackingGesturesManagement(true)
.accuracyColor(
ContextCompat.getColor(
activity!!,
com.innoventiq.arkbeh.R.color.colorGreen
)
)
.build()
}
val locationComponentActivationOptions =
activity?.let {
LocationComponentActivationOptions.builder(it, loadedMapStyle)
.locationComponentOptions(customLocationComponentOptions)
.build()
}
// Get an instance of the LocationComponent and then adjust its settings
mapboxMap?.locationComponent?.apply {
// Activate the LocationComponent with options
locationComponentActivationOptions?.let {
this?.activateLocationComponent(
it
)
}
// Enable to make the LocationComponent visible
isLocationComponentEnabled = true
// Set the LocationComponent's camera mode
cameraMode = CameraMode.TRACKING
// Set the LocationComponent's render mode
renderMode = RenderMode.COMPASS
}
} else {
permissionsManager = PermissionsManager(this)
permissionsManager.requestLocationPermissions(activity)
}
}
private fun getMyLocation() {
myCurrentLocation =
mapboxMap?.locationComponent?.lastKnownLocation?.latitude?.let {
mapboxMap!!.locationComponent.lastKnownLocation?.longitude?.let { it1 ->
LatLng(it,
it1
)
}
}
println("the location : $myCurrentLocation ")
}
override fun onStart() {
super.onStart()
mapView.onStart()
}
override fun onResume() {
super.onResume()
mapView.onResume()
}
override fun onPause() {
super.onPause()
mapView.onPause()
}
override fun onStop() {
super.onStop()
mapView.onStop()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
}
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}
companion object {
/**
* The fragment argument representing the section number for this
* fragment.
*/
private const val ARG_SECTION_NUMBER = "section_number"
/**
* Returns a new instance of this fragment for the given section
* number.
*/
#JvmStatic
fun newInstance(sectionNumber: Int): PlaceholderFragment {
return PlaceholderFragment().apply {
arguments = Bundle().apply {
putInt(ARG_SECTION_NUMBER, sectionNumber)
}
}
}
}
}
I used almost the full fragment code just trying to give you a clear view of the steps I've used to get the location. When it comes to the line println("the location : $myCurrentLocation ")
inside the getMyLocation() function , it returns this output the location : null ,, any help on this ?
Note
When the map loads, it shows my location perfectly and tracks it, but I just can't get the LatLng of it.
I've found the answer, used LocationComponent.LocationEngine.getLastLocation callBack to get it , here is the code:
private fun getMyLocation() {
mapboxMap?.locationComponent?.locationEngine?.getLastLocation(object :
LocationEngineCallback<LocationEngineResult> {
override fun onSuccess(result: LocationEngineResult?) {
if (result != null) {
myCurrentLocation =
LatLng(result.locations[0].latitude, result.locations[0].longitude)
println("my location is : $myCurrentLocation")
getTheRoute(myCurrentLocation)
}
}
override fun onFailure(exception: Exception) {
toast(getString(R.string.failed_get_location))
}
})
}

Categories

Resources