In this app, I have a logout button that's signed out the user if logged in with email & password or with a google account, the problem happens after signing out and re-launch the app again in the SplashActivity, I do check for the currentUser if is null it should go to the login page, else it should go to the home page, but It seems the app remember the current user even it's logged out before, I tried this solution but it doesn't work
Gif describes the problem
the dagger hilt module
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
#Singleton
fun provideFirebaseAuth() = FirebaseAuth.getInstance()
#Provides
#Singleton
fun provideFirebaseFirestore() = FirebaseFirestore.getInstance()
}
SplashActivity code
private const val TAG = "SplashActivity"
#AndroidEntryPoint
class SplashActivity : AppCompatActivity() {
private lateinit var binding: ActivitySplashBinding
#Inject
lateinit var firebaseAuth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySplashBinding.inflate(layoutInflater)
setContentView(binding.root)
val typeface = ResourcesCompat.getFont(this, R.font.blacklist)
val animation = AnimationUtils.loadAnimation(this, R.anim.my_anim)
binding.apply {
splashTextView.typeface = typeface
splashTextView.animation = animation
}
Log.d(TAG, "onCreate: ${firebaseAuth.currentUser?.providerData?.get(0)?.email}")
}
override fun onStart() {
super.onStart()
Handler(Looper.getMainLooper()).postDelayed({ /* Create an Intent that will start the Menu-Activity. */
if(firebaseAuth.currentUser == null) {
val intent = Intent(this#SplashActivity, MainActivity::class.java)
startActivity(intent)
this#SplashActivity.startActivity(intent)
this#SplashActivity.finish()
}else{
val intent = Intent(this#SplashActivity, HomeActivity::class.java)
startActivity(intent)
this#SplashActivity.startActivity(intent)
this#SplashActivity.finish()
}
}, 3000)
}
}
ProfileFragment "logout button code"
private const val TAG = "ProfileFragment"
#AndroidEntryPoint
class ProfileFragment : Fragment() {
private var _binding: FragmentProfileBinding? = null
private val binding get() = _binding!!
#Inject
lateinit var firebaseAuth: FirebaseAuth
private lateinit var googleSignInClient: GoogleSignInClient
private val viewModel by viewModels<CredentialsViewModel>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentProfileBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.logoutButton.setOnClickListener {
val currentUser = firebaseAuth.currentUser ?: return#setOnClickListener
for (i in 0 until currentUser.providerData.size) {
when (currentUser.providerData[i].providerId) {
"google.com" -> {
//User signed in with a custom account
Log.d(
TAG,
"onViewCreated: provider is ${currentUser.providerData[i].providerId}"
)
googleSignInClient = GoogleSignIn.getClient(
requireActivity(),
viewModel.getGoogleSignInOptions()
)
googleSignInClient.signOut().addOnCompleteListener {
if (it.isSuccessful) {
redirectToMainActivity()
}else {
return#addOnCompleteListener
}
}
}
"password" -> {
Log.d(TAG, "onViewCreated: ${currentUser.providerData[i].providerId}")
firebaseAuth.signOut()
redirectToMainActivity()
}
else -> {
Log.d(TAG, "onViewCreated: ${currentUser.providerData[i].providerId}")
redirectToMainActivity()
return#setOnClickListener
}
}
}
}
}
private fun redirectToMainActivity() {
val intent = Intent(requireContext(), MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
requireActivity().finish()
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
When a user is authenticated with email and password, in order to sign the user out, you only need to call:
firebaseAuth.signOut()
On the other hand, when you need to sign the user out from Google using:
googleSignInClient.signOut().addOnCompleteListener { /* ... /* }
You're only singing out the user from Google. This operation doesn't sign out the user from Firebase automatically. So to sign the user out completely, you have to use:
firebaseAuth.signOut() //Sign out from Firebase
googleSignInClient.signOut().addOnCompleteListener { /* ... /* } //Sign out from Google
Related
I need to convert the below chunk of codes to my viewModel so that I can follow the MVVM.
I did a lot of googling and see some so; solutions in StackOverflow which never worked for me and also not for Kotlin especially.
here is the fragment code with all the google authentication code in it:
class LoginFragment : Fragment() {
private var _binding: FragmentLoginBinding? = null
private val binding get() = _binding!!
private val scope = CoroutineScope(CoroutineName("loginFragmentScope"))
private val viewModel by viewModels<LoginViewModel>()
private lateinit var googleSignInClient: GoogleSignInClient
companion object {
const val TAG = "LoginFragment"
const val RC_SIGN_IN = 9139;
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentLoginBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.btnContinueWithGoogle.setOnClickListener {
UiUtils.showProgress(requireContext())
requestGoogleSignIn()
signIn()
UiUtils.hideProgress()
}
}
private fun requestGoogleSignIn() {
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build()
googleSignInClient = GoogleSignIn.getClient(requireActivity(), gso)
}
private fun signIn() {
val signInIntent = googleSignInClient.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
UiUtils.showProgress(requireContext())
if (requestCode == RC_SIGN_IN) {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
val account = task.getResult(ApiException::class.java)!!
Log.d(TAG, "firebaseAuthWithGoogle:" + account.id)
firebaseAuthWithGoogle(account.idToken!!)
} catch (e: ApiException) {
Log.w(TAG, "Google sign in failed", e)
UiUtils.hideProgress()
UiUtils.showErrorSnackBar(requireView(), "Google sign in failed", 0)
}
}
}
private fun firebaseAuthWithGoogle(idToken: String) {
val credential = GoogleAuthProvider.getCredential(idToken, null)
FirestoreAuth().auth.signInWithCredential(credential)
.addOnCompleteListener(requireActivity()) { task ->
if (task.isSuccessful) {
Log.d(TAG, "signInWithCredential:success")
FirestoreAuth().getUserDetails { user ->
FirestoreAuth().registerUserWithEmailAndPassword(user.email, "islamic123") {
if (it) {
updateUi(FirestoreAuth().auth.currentUser)
}
UiUtils.hideProgress()
}
DataStoreUtil(requireActivity()).storeInitialData(
user.userId,
user.firstName
)
updateUi(FirestoreAuth().auth.currentUser)
}
} else {
Log.w(TAG, "signInWithCredential:failure", task.exception)
UiUtils.hideProgress()
}
}
}
private fun updateUi(user: FirebaseUser? = null) {
if (user == null) {
Log.i(TAG, "User is Null!! Failed!")
return
}
startActivity(Intent(activity, TestActivity::class.java))
}
I guess it makes way more sense to have your logic like this:
View: onBtnClick(viewmodel.signIn())
Viewmodel: signIn() { FirebaseConnectorClass.doSignIn() }
FirebaseConnectorClass {
firebaseAuthWithGoogle()
}
EDIT:
To break it down very simple, with MVVM you want to divide your UI from the backend repositories.
The main goal is not to freeze the UI while doing some heavy lifting in the backend.
Therefore you have your view (UI) trigger an event in the viewModel. The viewModel then asks the backend to do the given task. When the backend is finished, it informs the viewModel and the viewModel updates the view (UI). This way your UI stays responsive all the time.
Checkout the links of Alex Mamo above to get a better understanding.
I am trying to use the Firebase API in my project but Transformations.map for the variable authenticationState in the View Model does not run. I have been following Google's tutorial here (link goes to the ViewModel of that project).
I want to be able to add the Transformations.map code to the FirebaseUserLiveData file later but I cant seem to figure out why it doesn't run.
FirebaseUserLiveData
class FirebaseUserLiveData: LiveData<FirebaseUser?>() {
private val firebaseAuth = FirebaseAuth.getInstance()
private val authStateListener = FirebaseAuth.AuthStateListener { firebaseAuth ->
value = firebaseAuth.currentUser
}
override fun onActive() {
firebaseAuth.addAuthStateListener { authStateListener }
}
override fun onInactive() {
firebaseAuth.removeAuthStateListener(authStateListener)
}
}
SearchMovieFragmentViewModel
class SearchMovieFragmentViewModel : ViewModel() {
enum class AuthenticationState {
AUTHENTICATED, UNAUTHENTICATED, INVALID_AUTHENTICATION
}
var authenticationState = Transformations.map(FirebaseUserLiveData()) { user ->
Log.d("TEST", "in the state function")
if (user != null) {
AuthenticationState.AUTHENTICATED
} else {
AuthenticationState.UNAUTHENTICATED
}
}
SearchMovieFragment
class SearchMovieFragment : Fragment(), MovieSearchItemViewModel {
companion object {
fun newInstance() = SearchMovieFragment()
}
private lateinit var searchMovieFragmentViewModel: SearchMovieFragmentViewModel
private lateinit var binding: SearchMovieFragmentBinding
private lateinit var movieRecyclerView: RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.search_movie_fragment, container, false)
searchMovieFragmentViewModel = ViewModelProvider(this).get(SearchMovieFragmentViewModel::class.java)
binding.lifecycleOwner = this
binding.viewmodel = searchMovieFragmentViewModel
binding.signOutButton.setOnClickListener {
AuthUI.getInstance().signOut(requireContext())
}
searchMovieFragmentViewModel.authenticationState.observe(viewLifecycleOwner, Observer { state ->
when (state) {
AUTHENTICATED -> searchMovieFragmentViewModel.signedIn = View.VISIBLE
UNAUTHENTICATED -> searchMovieFragmentViewModel.signedIn = View.GONE
}
})
return binding.root
}
}
Should be .addAuthStateListener(authStateListener) instead of { authStateListener }
That is because you are not keeping the reference of FirebaseUserLiveData() once you start observing it like Transformations.map(FirebaseUserLiveData()) { user ->.
You have to have the reference of the Livedata you are mapping or transferring to another form of Livedata.
It is like a chain of observation, All LiveData in the chain should be observed or should have some kind of observer down the line, The main use-case is to transform some form of livedata to something you want, For Example:
class YourRepository{ // your repo, that connected to a network that keeps up to date some data
val IntegerResource: LiveData<Int> = SomeRetrofitInstance.fetchFromNetwork() //updating some resource from network
}
class YourViewModel{
val repository = YourRepository()
//this will start observe the repository livedata and map it to string resource
var StringResource: Livedata<String> = Transformations.map( repository.IntegerResource ) { integerValue ->
integerValue.toString()
}
My Point is you have to keep alive the LiveData you are transforming. Hope helped.
I have one activity with unspecified orientation and there is one fragment attached to that activity that has different layouts for portrait and landscape mode and on that fragment, multiple API calls on a conditional basis, my problem is that when the screen rotates all data was lost and there is a lot of data on that fragment by which I don't want to save each data on saveInstance method. I tried android:configChanges="keyboardHidden|orientation|screenSize", but this didn't solve my problem. I want to handle this problem using viewModel. Please help, Thanks in advance.
Here is my code
Repository
class GetDataRepository {
val TAG = GetDataRepository::class.java.canonicalName
var job: CompletableJob = Job()
fun getData(
token: String?,
sslContext: SSLContext,
matchId: Int
): LiveData<ResponseModel> {
job = Job()
return object : LiveData<ResponseModel>() {
override fun onActive() {
super.onActive()
job.let { thejob ->
CoroutineScope(thejob).launch {
try {
val apiResponse = ApiService(sslContext).getData(
token
)
LogUtil.debugLog(TAG, "apiResponse ${apiResponse}")
withContext(Dispatchers.Main) {
value = apiResponse
}
} catch (e: Throwable) {
LogUtil.errorLog(TAG, "error: ${e.message}")
withContext(Dispatchers.Main) {
when (e) {
is HttpException -> {
value =
Gson().fromJson<ResponseModel>(
(e as HttpException).response()?.errorBody()
?.string(),
ResponseModel::class.java
)
}
else -> value = ResponseModel(error = e)
}
}
} finally {
thejob.complete()
}
}
}
}
}
}
fun cancelJob() {
job.cancel()
}
}
ViewMode:
class DataViewModel : ViewModel() {
val TAG = DataViewModel::class.java.canonicalName
var mListener: DataListener? = null
private val mGetDataRepository: GetDataRepository = GetDataRepository()
fun getData() {
LogUtil.debugLog(TAG, "getData")
if (mListener?.isInternetAvailable()!!) {
mListener?.onStartAPI()
val context = mListener?.getContext()
val token: String? = String.format(
context?.resources!!.getString(R.string.user_token),
PreferenceUtil.getUserData(context).token
)
val sslContext = mListener?.getSSlContext()
if (sslContext != null) {
val getData =
mGetDataRepository.getData(
token
)
LogUtil.debugLog(TAG, "getData ${getData}")
mListener?.onApiCall(getData)
} else {
LogUtil.debugLog(TAG, "getData Invalid certificate")
mListener?.onError("Invalid certificate")
}
} else {
LogUtil.debugLog(TAG, "getData No internet")
mListener?.onError("Please check your internet connectivity!!!")
}
LogUtil.debugLog(TAG, "Exit getData()")
}
}
Activity:
class DataActivity : AppCompatActivity() {
val TAG = DataActivity::class.java.canonicalName
lateinit var fragment: DataFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LogUtil.debugLog(TAG, "onCreate: Enter")
var binding: ActivityDataBinding =
DataBindingUtil.setContentView(this, R.layout.activity_data)
if (savedInstanceState == null) {
fragment = DataFragment.newInstance()
supportFragmentManager.beginTransaction().add(R.id.container, fragment, DataFragment.TAG)
} else {
fragment = supportFragmentManager.findFragmentByTag(DataFragment.TAG) as DataFragment
}
LogUtil.debugLog(TAG, "onCreate: Exit")
}
}
Fragment:
class DataFragment : Fragment(), DataListener {
private var mBinding: FragmentDataBinding? = null
private lateinit var mViewModel: DataViewModel
companion object {
val TAG = DataFragment::class.java.canonicalName
fun newInstance(): DataFragment {
return DataFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mBinding =
DataBindingUtil.inflate(inflater, R.layout.fragment_data, container, false)
mViewModel = ViewModelProvider(this).get(DataViewModel::class.java)
mViewModel.mListener = this
getData()
return mBinding?.root
}
private fun getData() {
LogUtil.debugLog(TAG, "Enter getMatchScore()")
mViewModel.getData()
LogUtil.debugLog(TAG, "Exit getMatchScore()")
}
override fun <T> onApiCall(response: LiveData<T>) {
response.observe(this, Observer {
it as DataResponseModel
//
})
}
}
The lifecycle of viewModel by default is longer than your activity (in your case, screen rotation).
ViewModel will not be destroyed as soon as activity destroyed for configuration change, you can see this link.
You seem to have made a mistake elsewhere in your activity/fragment, please put your activity/fragment code here.
In your fragment you call mViewModel.getData() in your onCreateView, and every time you rotate your activity, this method call and all store data reset and fetched again!, simply you can check data of ViewModel in your fragment and if it's empty call getData(), it also seems your ViewModel reference to your view(Fragment) (you pass a listener from your fragment to your ViewModel) and it is also an anti-pattern (This article is recommended)
So I have followed the advice of 'yoursTruly' and created an AuthStateListener in my Activity (as described here: https://www.youtube.com/watch?v=6CXUNcsQPgQ&feature=youtu.be).
This works really well, in the fact that the Activity is simply listening to Authentication and will divert to next Activity if someone logs in and the Fragment simply calls signIn(email, password) via the ViewModel.
However, can not see a way to look for failed login attempts (so the UI will just look like it is unresponsive).
My structure is as follows: Activity -> Fragment -> ViewModel -> Repository.
I'm using DataBinding & Navigation.
Activity
class LoginActivity : AppCompatActivity(), FirebaseAuth.AuthStateListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.login_activity)
}
override fun onStart() {
super.onStart()
FirebaseAuth.getInstance().addAuthStateListener(this)
}
override fun onStop() {
super.onStop()
FirebaseAuth.getInstance().removeAuthStateListener(this)
}
override fun onAuthStateChanged(firebaseAuth: FirebaseAuth) {
// Will only fire if state has changed!
if (FirebaseAuth.getInstance().currentUser == null) {
Toast.makeText(this,"Welcome to the Locators App!\n\nPlease login to continue", Toast.LENGTH_LONG).show()
return
}
firebaseAuth.currentUser?.getIdToken(true)
?.addOnSuccessListener { result ->
val idToken = result.token
Toast.makeText(this,"User Signed In", Toast.LENGTH_LONG).show()
Log.d(TAG, "GetTokenResult result (check this at https://jwt.io/ = $idToken")
goToSiteActivity()
}
}
private fun goToSiteActivity() {
val intent = Intent(this, SiteActivity::class.java)
startActivity(intent)
finish()
}
}
Fragment
class LoginFragment : Fragment() {
private lateinit var loginViewModel: LoginViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val binding: LoginFragmentBinding = DataBindingUtil.inflate(
inflater, R.layout.login_fragment, container, false)
binding.apply {
loginPasswordResetText.setOnClickListener{
findNavController().navigate(R.id.action_loginFragment_to_loginPasswordResetFragment)
}
loginButton.setOnClickListener{
loginProgressBar.visibility = View.VISIBLE
val email = loginEmailEditText.text.toString()
val password = loginPasswordEditText.text.toString()
if(validateForm(email, password)) {
loginViewModel.loginUser(email, password)
}
loginProgressBar.visibility = View.GONE
}
loginNewUserText.setOnClickListener{
findNavController().navigate(R.id.action_loginFragment_to_loginUserRegisterFragment)
}
}
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
loginViewModel = ViewModelProviders.of(requireActivity()).get(LoginViewModel::class.java)
}
private fun validateForm(email: String, password: String): Boolean {
var validForm = true
if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
loginEmailEditText.error = "Please enter valid email address!"
validForm = false
} else loginEmailEditText.error = null
if (password.isEmpty()) {
loginPasswordEditText.error = "Please enter password!"
validForm = false
} else loginPasswordEditText.error = null
Log.d(TAG,"validateForm: (email = $email, password = $password, validateForm = $validForm)")
return validForm
}
}
ViewModel
class LoginViewModel : ViewModel() {
fun loginUser (email: String, password: String) {
firestoreRepository.loginUser(email, password)
}
}
Repository
class FirestoreRepository {
var firebaseAuth = FirebaseAuth.getInstance()
var firebaseUser = firebaseAuth.currentUser
var failedLogin: Boolean = false
fun loginUser(email: String, password: String) {
failedLogin = false
firebaseAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener {
if (it.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithEmail:success")
} else {
// If sign in fails, display a message to the user.
Log.d(TAG, "signInWithEmail:failure", it.exception)
failedLogin = true
}
}
}
}
This question ties in with a larger query of, if you create LiveData in Repo, how do you Observe from ViewModel (i.e. what do you use as the LifeCycleOwner?), but I will ask on a seperate question..
An AuthStateListener is not sufficient to determine when a sign-in fails. It will only tell you when the state of the user changes between signed in and signed out.
You will have to use the result of firebaseAuth.signInWithEmailAndPassword to determine if the sign-in failed. It looks like you already have some code there to handle that case, but you're not doing much with the error other than setting a property.
What you should do instead is have your loginUser ViewModel method return a LiveData that gets notified when the sign-in succeeds or fails. You will have to wire up the call to signInWithEmailAndPassword to change the state of that LiveData, and your view will have to observe that LiveData to show a message to the user if necessary.
I'm using MVVM as architecture, also the repository pattern. I have a Web service, a room database also. Using coroutines block any button I click.
There's a list/detail implemented with a fragment and an activity respectively.
I can figure out what's wrong in the way I implemented the coroutines and Viewmodel.
class BuySharedViewModel(application: Application) : AndroidViewModel(application) {
private val repository: BuyRepository
var allBuys: LiveData<List<Buy>>
init {
val buyDao = KunukRoomDatabase.getDatabase(application, viewModelScope).buyDao()
val buyRemote = BuyRemote()
repository = BuyRepository.getInstance(buyDao , buyRemote)
//Use async because it return a result
viewModelScope.launch { getAllBuys() }
allBuys = buyDao.loadAllBuys()
}
private suspend fun getAllBuys() {
repository.getBuys()
}
}
Here's is the Repository, it take data from web service and add it to the room database, while ViewModel get's data from room database.
class BuyRepository (private val buyDao: BuyDao, private val buyRemote: BuyRemote) {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Default + job)
companion object {
//For singleton instantiation
#Volatile private var instance: BuyRepository? = null
fun getInstance(buyDao: BuyDao, buyRemote: BuyRemote) =
instance ?: synchronized(this) {
instance ?: BuyRepository(buyDao, buyRemote)
.also { instance = it}
}
}
suspend fun getBuys(){
refresh()
}
private suspend fun refresh(){
try {
val list = scope.async { buyRemote.loadBuys() }
list.await().forEach { buy -> insert(buy) }
} catch (e: Throwable) {}
}
#WorkerThread
private fun insert(buy: Buy) {
buyDao.insertBuy(buy)
}
}
The fragment work, data are displayed, when i click on an item from that fragment(recyclerView) it work, the activity display details data. But none of the click on that activity works, like it doesn't detect the clicks. I guess it got something to do with the coroutines because when I comment out the code viewmodelScope.launch { getAllBuys()} from the BuySharedViewModel it works, because it load data from the previous call from room database, and the clicks works.
Here's the code in the detail view:
class BuyDetailActivity : AppCompatActivity() {
private lateinit var sharedViewModel: BuySharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lateinit var buy: Buy
sharedViewModel = ViewModelProviders.of(this).get(BuySharedViewModel::class.java)
val position = intent.getIntExtra("position", 0)
sharedViewModel.allBuys.observe(this, Observer<List<Buy>> { buys ->
buy = buys[position]
val binding: com.example.drake.kunuk.databinding.ActivityBuyDetailBinding =
DataBindingUtil.setContentView(this, com.example.drake.kunuk.R.layout.activity_buy_detail)
binding.buy = buy
val agentNumber = buy.agentNumber?:"+50937438713"
bnvContactAgent.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
com.example.drake.kunuk.R.id.action_call -> {
val callNumberUri = Uri.parse("tel:$agentNumber")
val callIntent = Intent(Intent.ACTION_DIAL, callNumberUri)
startActivity(callIntent)
}
com.example.drake.kunuk.R.id.action_sms -> {
val smsNumberUri = Uri.parse("sms:$agentNumber")
val smsIntent = Intent(Intent.ACTION_SENDTO, smsNumberUri)
startActivity(smsIntent)
}
com.example.drake.kunuk.R.id.action_email -> {
val uriText = "mailto:drakecolin#gmail.com" +
"?subject=" + Uri.encode("I'm interested in $agentNumber") +
"&body=" + Uri.encode("Hello, ")
val uri = Uri.parse(uriText)
val sendIntent = Intent(Intent.ACTION_SENDTO)
sendIntent.data = uri
startActivity(Intent.createChooser(sendIntent, "Send email"))
}
}
false
}
This is the code of my fragment:
class BuyFragment : Fragment() {
companion object {
fun newInstance() = BuyFragment()
}
private lateinit var viewModel: BuySharedViewModel
private val buyList = ArrayList<Buy>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Get a new or existing ViewModel from the ViewModelProvider.
viewModel = ViewModelProviders.of(this).get(BuySharedViewModel::class.java)
// Add an observer on the LiveData returned by loadAllBuys.
// The onChanged() method fires when the observed data changes and the activity is
// in the foreground.
viewModel.allBuys.observe(this, Observer<List<Buy>> { buys ->
// Update the cached copy of the words in the adapter.
buys?.let { (rvBuy.adapter as BuyAdapter).setBuys(it) }
progressBar.visibility = View.GONE
})
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.buy_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
rvBuy.layoutManager = LinearLayoutManager(context)
rvBuy.adapter = BuyAdapter(activity!!.applicationContext,
R.layout.buy_card, buyList)
progressBar.visibility = View.VISIBLE
}
}
This is the code for the BuyDao:
#Dao
interface BuyDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertBuy(vararg buys: Buy)
#Update
fun updateBuy(vararg buys: Buy)
#Delete
fun deleteBuys(vararg buys: Buy)
#Query("SELECT * FROM buys")
fun loadAllBuys(): LiveData<List<Buy>>
#Query("DELETE FROM buys")
suspend fun deleteAll()
}
viewModelScope by default uses Dispatchers.Main and it is blocking your UI.
Try this:
viewmodelScope.launch(Dispatchers.IO) { getAllBuys()}
Edit:
The problem is your setting listner on BottomNavigation when your livedata is updated which is causing this weird issue.
Replace your BuyDetailActivity code with this:
class BuyDetailActivity : AppCompatActivity() {
private lateinit var sharedViewModel: BuySharedViewModel
private var agentNumber = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityBuyDetailBinding =
DataBindingUtil.setContentView(this, R.layout.activity_buy_detail)
binding.buy = Buy()
lateinit var buy: Buy
sharedViewModel = ViewModelProviders.of(this).get(BuySharedViewModel::class.java)
val position = intent.getIntExtra("position", 0)
sharedViewModel.allBuys.observe(this, Observer<List<Buy>> { buys ->
buy = buys[position]
binding.buy = buy
binding.executePendingBindings()
agentNumber = buy.agentNumber
// set animation duration via code, but preferable in your layout files by using the animation_duration attribute
expandableTextView.setAnimationDuration(750L)
// set interpolators for both expanding and collapsing animations
expandableTextView.setInterpolator(OvershootInterpolator())
// or set them separately.
expandableTextView.expandInterpolator = OvershootInterpolator()
expandableTextView.collapseInterpolator = OvershootInterpolator()
// toggle the ExpandableTextView
buttonToggle.setOnClickListener {
buttonToggle.setText(if (expandableTextView.isExpanded) com.example.drake.kunuk.R.string.more else com.example.drake.kunuk.R.string.less)
expandableTextView.toggle()
}
// but, you can also do the checks yourself
buttonToggle.setOnClickListener {
if (expandableTextView.isExpanded) {
expandableTextView.collapse()
buttonToggle.setText(com.example.drake.kunuk.R.string.more)
} else {
expandableTextView.expand()
buttonToggle.setText(com.example.drake.kunuk.R.string.less)
}
}
//Open photoView activity when clicked
ivHouseDetail.setOnClickListener {
applicationContext
.startActivity(
Intent(
applicationContext,
ViewPagerActivity::class.java
)
.putExtra("imageList", buy.propertyImage)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
}
})
findViewById<BottomNavigationView>(R.id.bnvContactAgent)?.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.action_call -> {
Log.e("BIRJU", "Action call")
val callNumberUri = Uri.parse("tel:$agentNumber")
val callIntent = Intent(Intent.ACTION_DIAL, callNumberUri)
startActivity(callIntent)
}
R.id.action_sms -> {
Log.e("BIRJU", "Action SMS")
val smsNumberUri = Uri.parse("sms:$agentNumber")
val smsIntent = Intent(Intent.ACTION_SENDTO, smsNumberUri)
startActivity(smsIntent)
}
R.id.action_email -> {
Log.e("BIRJU", "Action Email")
val uriText = "mailto:drakecolin#gmail.com" +
"?subject=" + Uri.encode("I'm interested in $agentNumber") +
"&body=" + Uri.encode("Hello, ")
val uri = Uri.parse(uriText)
val sendIntent = Intent(Intent.ACTION_SENDTO)
sendIntent.data = uri
startActivity(Intent.createChooser(sendIntent, "Send email"))
}
}
false
}
}
}