Document references must have an even number of segments - kotlin

Error: Document references must have an even number of segments, but Users has 1
I have been looking through different posts on here and on different forums but all have the problem when first loading but my problem is after I logout or reset the password. When I load the contents from firebase I get the information but when I click on the sign out then go to login again it crash's and I get this error. I have logged the users.uid and Document references and does not change after logging out.
My collection path is done with Constants so I don't have a mis type.
I have found that the error is in the Fragment side of my app in the FirestoreClass().loadUserData_fragment(this)
As commenting this line out after the log out will allow the app to run but in the activity the data can still be loaded as the activity load data and the fragment is the same so I don't get why it wouldn't load into the fragment after the sign out but will load first time.
Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
FirestoreClass().loadUserData_fragment(this)
}
Activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUpdateProfileBinding.inflate(layoutInflater)
val view : LinearLayout = binding.root
setContentView(view)
setupActionBar()
FirestoreClass().loadUserData(this)
}
GetCurrentUserID
fun getCurrentUserID():String{
// auto login
var currentUser = FirebaseAuth.getInstance().currentUser
var currentUserId = ""
if (currentUser != null){
currentUserId = currentUser.uid
Log.i("uis",currentUser.uid)
}
return currentUserId
}
Activity version
fun loadUserData(activity:Activity){
mFireStore.collection(Constants.USERS)
.document(getCurrentUserID())
.get()
.addOnSuccessListener { document ->
val loggedInUser = document.toObject(User::class.java)!!
Log.i("uis",getCurrentUserID() + Constants.USERS)
when(activity){
is UpdateProfileActivity ->{
activity.setUserDataInUI(loggedInUser)
}
is LoginActivity -> {
// Call a function of base activity for transferring the result to it.
activity.userLoggedInSuccess(loggedInUser)
}
}
}
}
Fragment version
fun loadUserData_fragment(fragment: Fragment){
mFireStore.collection(Constants.USERS)
.document(getCurrentUserID())
.get()
.addOnSuccessListener { document ->
val loggedInUser = document.toObject(User::class.java)!!
Log.i("uis",getCurrentUserID() + Constants.USERS)
when(fragment){
is HomeFragment ->{
fragment.setUserDataInUIFragment(loggedInUser)
}
}
}
}

It seems that your getCurrentUserID() returns no value, which you're not handling in your code. The best option is to only call loadUserData when there is an active user, but alternatively you can also check whether getCurrentUserID() returns a value:
fun loadUserData(activity:Activity){
if (getCurrentUserID() != "") { // 👈
mFireStore.collection(Constants.USERS)
.document(getCurrentUserID())
.get()
.addOnSuccessListener { document ->
...
}
}
}

Related

Jetpack Compose not updating / recomposing Flow List Values from Room DB when DB Data is getting changed

I'm trying to show a List of Items in my Android App. I'm using Jetpack Compose, Flows and RoomDB.
When launching the Activity all Items are shown without any problems, the Flow get's items collected and they are displayed.
But when I change some properties of the Item in the Database, the changes are not displayed. In my case I change the item to deleted, but it's still displayed as not deleted.
When I look at the Database Inspector, the value is changed in the database and set to deleted.
When I log collecting the flow, the change is getting emitted (It says the Item is set to deleted)
But Jetpack Compose is not recomposing the change.
If I delete an element from / add an element to the List (in the DB) the UI gets updated and recomposed.
So I can only assume that the problem must lie in the recomposition or handling of the flow.
Here my Code:
My Activity:
#AndroidEntryPoint
class StockTakingHistoryActivity : ComponentActivity() {
private val viewModel: StockTakingHistoryViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.stockList = ...
setContent {
LaunchedEffect(Unit) {
viewModel.getStockListItems(viewModel.stockList!!.uuid)
}
Surface(color = MaterialTheme.colors.background) {
Content(viewModel.stockListItems)
}
}
}
}
...
#Composable
private fun Content(items: List<StockListItem>) {
...
LazyColumn {
items(items) { item ->
HistoryItem(stockListItem = item)
}
}
}
}
...
#Composable
private fun HistoryItem(stockListItem: StockListItem) {
...
Text(text = stockListItem.deleted)
...
Button(onClick = {
viewModel.deleteItem(stockListItem)
}) {
Text(text = "Set to deleted!")
}
}
}
My ViewModel:
var stockListItems by mutableStateOf(emptyList<StockListItem>())
fun getStockListItems(uuid: String) {
viewModelScope.launch {
stockListItemRepository.findByUUID(uuid).collect { items ->
Log.d("StockTakingHistoryViewModel", "items changed! ${items.map { it.deleted }}")
stockListItems = items
}
}
}
fun deleteItem(stockListItem: StockListItem) {
viewModelScope.launch(Dispatchers.IO) {
stockListItemRepo.update(item.copy(deleted = true);
}
}
The Repository:
fun findByUUID(uuid: String): Flow<List<StockListItem>> {
return dao.findByUUID(uuid)
}
The Dao behind the Repository Request:
#Query("select * from StockListItem where stockListUUID = :uuid order by createdAt desc limit 30")
fun findByUUID(uuid: String): Flow<List<StockListItem>>
I would be very happy if someone could help me! Thank you!
Considering you can collect a flow as state (via collectAsState) I'd consider going that route for getting the list rather than calling collect in the viewModel and updating the stockListItems as there are fewer moving parts for things to go wrong.
For example something like the following:
setContent {
val stockListItems = viewModel.getStockListItemsFlow(uuid).collectAsState(initial = emptyList())
Surface(color = MaterialTheme.colors.background) {
Content(stockListItems)
}
}
Found the Problem: The equals() method of StockListItem only compared the primary key.

Change API param based on Dialog Fragment input with MVVM in Kotlin

i'm a beginner in android & kotlin and i'm having an issue i been trying to figure out all day...
I have an app that fetches data from NewsApi and displays it in a recycler view , i am using Retrofit library and Room (to save favorite articles) with MVVM architecture. I want to add an option so that the user can select the country of the news from a dialog that pops up by clicking on a icon on the toolbar menu.
I have created a custom DialogFragment and have it show up, the dialog contains a spinner with a list of countries and i'm using FragmentResult and FragmentResultListener to pass the country value between dialog fragment and news fragment.
DialogFragment
class CountrySelectDialog : DialogFragment(R.layout.country_selection_dialog) {
private lateinit var binding: CountrySelectionDialogBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = CountrySelectionDialogBinding.bind(view)
binding.spCountrySelection.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
adapterView: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
Toast.makeText(
context,
"you selected ${adapterView?.getItemAtPosition(position).toString()}",
Toast.LENGTH_SHORT
).show()
}
override fun onNothingSelected(adapterView: AdapterView<*>?) {
}
}
binding.btnCancel.setOnClickListener {
this.dismiss()
}
binding.btnConfirm.setOnClickListener {
val result = binding.spCountrySelection.selectedItem.toString()
setFragmentResult("countryCode", bundleOf("bundleKey" to result))
this.dismiss()
}
}
}
The news Fragment is observing data from the View Model
class BreakingNewsFragment : Fragment(R.layout.fragment_breaking_news) {
lateinit var viewModel: NewsViewModel
lateinit var newsAdapter: NewsAdapter
private lateinit var binding: FragmentBreakingNewsBinding
val TAG = "BreakingNewsFragment"
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentBreakingNewsBinding.bind(view)
viewModel = (activity as NewsActivity).viewModel
setUpRecyclerView()
setFragmentResultListener("countryCode") { countryCode, bundle ->
val result = bundle.getString("countryCode")
viewModel.countryCode = result!!}
viewModel.breakingNews.observe(viewLifecycleOwner, Observer {
when (it) {
is Resource.Success -> {
hideProgressBar()
it.data?.let {
newsAdapter.differ.submitList(it.articles.toList())
val totalPages = it.totalResults / QUERY_PAGE_SIZE + 2
isLastPage = viewModel.breakingNewsPage == totalPages
}
}
is Resource.Error -> {
hideProgressBar()
it.message?.let {
Log.e(TAG, "An error occurred: $it")
}
}
is Resource.Loading -> {
showProgressBar()
}
}
})
newsAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("article", it)
}
findNavController().navigate(
R.id.action_breakingNewsFragment_to_articleFragment, bundle
)
}
}
ViewModel:
class NewsViewModel(val newsRepository: NewsRepository, val app: Application) : AndroidViewModel(app) {
val breakingNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var breakingNewsPage = 1
var breakingNewsResponse: NewsResponse? = null
val searchNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var searchNewsPage = 1
var searchNewsResponse: NewsResponse? = null
var countryCode :String = "it"
init {
getBreakingNews(countryCode)
}
fun getBreakingNews(countryCode: String) {
viewModelScope.launch {
breakingNews.postValue(Resource.Loading())
val response = newsRepository.getBreakingNews(countryCode, breakingNewsPage)
breakingNews.postValue(handleBreakingNewsResponse(response))
}
}
fun handleBreakingNewsResponse(response: Response<NewsResponse>): Resource<NewsResponse> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
breakingNewsPage++
if (breakingNewsResponse == null) {
breakingNewsResponse = resultResponse
} else {
val oldArticles = breakingNewsResponse?.articles
val newArticles = resultResponse.articles
oldArticles?.addAll(newArticles)
}
return Resource.Success(breakingNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.message())
}
fun searchNews(searchQuery: String) {
viewModelScope.launch {
searchNews.postValue(Resource.Loading())
val response = newsRepository.searchNews(searchQuery, searchNewsPage)
searchNews.postValue((handleSearchNewsResponse(response)))
}
}
fun handleSearchNewsResponse(response: Response<NewsResponse>): Resource<NewsResponse> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
searchNewsPage++
if (searchNewsResponse == null) {
searchNewsResponse = resultResponse
} else {
val oldArticles = searchNewsResponse?.articles
val newArticles = resultResponse.articles
oldArticles?.addAll(newArticles)
}
return Resource.Success(searchNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.message())
}
}
When i click on the icon on the toolbar menu the dialog appears and works fine but i can't seem to find a way to have the recycler view update with new data using given value for country
I searched everywhere and couldn't find a solution (or probably didn't understand it :S) can someone guide me into the right direction? I'm so lost...
When I click on the icon on the toolbar menu the dialog appears and works fine but I can't seem to find a way to have the recycler view update with new data using given value for country.

Kotlin: Is it possible to make a function, which calls a retrofit service, to return a String value?

I have a Fragment and a View Model.
The layout of the Fragment contains a button.
When the button is clicked, we try to get an API response, which contains a url.
That url is used to start an intent to open a web page.
I am currently accomplishing this with event driven programming.
The button in the Fragment is clicked.
The function in the view model is called to get the API response, which contains the url.
The url in the view model is assigned as live data, which is observed in the fragment.
The fragment observes the url live data has changed. It attempts to launch the WebView with the new url.
Can the Fragment skip Observing for the url and directly get the ViewModel function to return a string?
Here is the code for the Fragment:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Set the OnClickListener
myButton.setOnClickListener {
myViewModel.getUrlQueryResults()
}
// Observables to open WebView from Url
myViewModel.myUrl.observe(viewLifecycleOwner, Observer {
it?.let{
if (it.isEmpty()) {
// No Url found in this API response
}
else {
// Open the WebView
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(it))
startActivity(intent)
}
catch (e: Exception) {
// Log the catch statement
}
}
}
})
}
Here is the code for the ViewModel:
// Live data observed in fragment. When this changes, fragment will attempt to launch Website with the url
private val _myUrl = MutableLiveData<String>()
val myUrl: LiveData<String>
get() = _myUrl
// Possible to make this return a string?
fun getUrlQueryResults() {
InfoQueryApi.retrofitService.getInfo(apiKey).enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
try {
// Store the response here
apiResponse = parseInfoJsonResult(JSONObject(response.body()!!))
// Grab the url from the response
var urlFromResponse = apiResponse?.url
if (urlFromResponse.isNullOrEmpty()) {
urlFromResponse = ""
}
// Store the urlFromResponse in the live data so Fragment can Observe and act when the value changes
_myUrl.value = urlFromResponse
} catch (e: Exception) {
// Log catch statement
}
}
override fun onFailure(call: Call<String>, t: Throwable) {
// Log error
}
})
}

android livedata not working in Activity , viewmodel

Hello I have recently encountered a situation where observing is not possible in livedata, so I am going to ask a question
It's too basic, but I don't know why it doesn't work, so I need your help.
If you give me a little teaching, I would be grateful
my SignUpActivity
class SignUpActivity : BaseKotlinActivity<ActivitySignUpBindingImpl, SignUpViewModel>() {
override val layoutResourceId: Int get() = R.layout.activity_sign_up
override val viewModel: SignUpViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivitySignUpBindingImpl>(this, layoutResourceId)
binding.apply {
lifecycleOwner = this#SignUpActivity
signUpViewModel = viewModel
}
viewModel?.apply {
signUpStep.observe(this#SignUpActivity, Observer {
when (it) {
SignUpStep.SIGN_UP -> supportFragmentManager.beginTransaction().replace(R.id.fragment, SignUpFragment(), "SignUpFragment").commit()
SignUpStep.PASSWORD -> supportFragmentManager.beginTransaction().replace(R.id.fragment, SignUpPasswordFragment(), "SignUpPasswordFragment").commit()
SignUpStep.PHONE_CERTIFICATION -> supportFragmentManager.beginTransaction().replace(R.id.fragment, SignUpPhoneCertificationFragment(), "SignUpPhoneCertificationFragment").commit()
else -> Unit
}
Log.d("Test Checked1", "${signUpStep.value}")
})
}
}
}
my viewModel
private val _signUpStep = MutableLiveData<SignUpStep>(SignUpStep.SIGN_UP)
val signUpStep: LiveData<SignUpStep>
get() = _signUpStep
fun moveStep(view: View, newSignUpStep: SignUpStep) {
val oldSignUpStep = _signUpStep.value
_signUpStep.value = newSignUpStep
Log.d( "Test Checked","moveStep: $oldSignUpStep -> $newSignUpStep")
}
log
Test Checked1: SIGN_UP
moveStep: SIGN_UP -> PASSWORD
moveStep: PASSWORD -> PASSWORD
moveStep: PASSWORD -> PASSWORD
If you check the log, you can see that the moveStep changes normally. Then signUpSteop has changed normally, but it is not received in the observe of livedata, because the screen does not move and the log does not appear.
I'm just wondering if the code is wrong or what's wrong. Can you help me?
For reference, signUpStep is changing in Fragment and livedata is constantly being observed in activity.

LocalDate.format causes OutofBount Exception in Observer

I'm making an app in android using Kotlin, Material Design Components and the new architecture components.
I have an activity that starts a DialogFragment onCreate
The fragment has 6 Views that, via an observer, observe a different LiveDate for each and every one.
While checking all the this setup work I noticed that after 7 view switching I get
2020-05-12 20:43:19.346 4778-4778/package E/InputEventReceiver: Exception dispatching input event.
2020-05-12 20:43:19.346 4778-4778/package E/MessageQueue-JNI: Exception in MessageQueue callback: handleReceiveCallback
2020-05-12 20:43:19.357 4778-4778/package E/MessageQueue-JNI:
java.lang.ArrayIndexOutOfBoundsException: length=9; index=9
at android.text.Layout$HorizontalMeasurementProvider.get(Layout.java:1589)
...
I cheked the following things:
did all the setup on only one view -> still crashes
did all the setup on only one view but without using the "createDateFieldObserver" method -> still carshes
not calling the observer -> no crash
calling the observer but without the LocalDate.format -> no crash
I concluded the probleme is in the format function but I do not understand why.
The error is not pointing in that direction.
Any ideas?
Activity code
class UITestingActivity: FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_greenhouse)
val dialog = LabTimesDialogFragment()
dialog.show(supportFragmentManager, "LabTimes")
}
}
Fragment Code
class TimesDialogFragment : DialogFragment() {
companion object Companion {
private val TAG: String = "TimesDialog"
}
private val datesViewModel: TimesViewModel by activityViewModels()
private lateinit var datesViews: Map<LiveData<LocalDate>, TextInputEditText>
override fun onCreateDialog(savedInstanceState: Bundle?) : Dialog {
val viewsArray: Array<TextInputEditText>
return activity?.let {
val builder = AlertDialog.Builder(it)
val inflater = requireActivity().layoutInflater
val rootView: View = inflater.inflate(R.layout.dialog_filter_times, null)
builder.setView(rootView)
.setPositiveButton(R.string.feed) { dialog, id -> closeDialog() }
.setNegativeButton(R.string.cancel) { dialog, id -> getDialog()?.cancel() }
val dialog: AlertDialog = builder.create()
val fromSampling: TextInputEditText = rootView.findViewById(R.id.from_sampling) ?: throw IllegalStateException("Missing date view in LabTimesFilterDialog")
val toSampling: TextInputEditText = rootView.findViewById(R.id.to_sampling) ?: throw IllegalStateException("Missing date view in LabTimesFilterDialog")
val fromSending: TextInputEditText = rootView.findViewById(R.id.from_sending) ?: throw IllegalStateException("Missing date view in LabTimesFilterDialog")
val toSending: TextInputEditText = rootView.findViewById(R.id.to_sending) ?: throw IllegalStateException("Missing date view in LabTimesFilterDialog")
val fromReceiving: TextInputEditText = rootView.findViewById(R.id.from_receiving) ?: throw IllegalStateException("Missing date view in LabTimesFilterDialog")
val toReceiving: TextInputEditText = rootView.findViewById(R.id.to_receiving) ?: throw IllegalStateException("Missing date view in LabTimesFilterDialog")
datesViews = mapOf(datesViewModel.fromSampling to fromSampling,
datesViewModel.toSampling to toSampling,
datesViewModel.fromSending to fromSending,
datesViewModel.toSending to toSending,
datesViewModel.fromReceiving to fromReceiving,
datesViewModel.toReceiving to toReceiving
)
for ((liveData, textView) in datesViews) {
liveData.observe(this, createDateFieldObserver(textView))
textView.setOnClickListener { v ->
Log.d(TAG, "hello"+v.id)
}
}
return dialog
} ?: throw IllegalStateException("Activity cannot be null")
}
private fun closeDialog() {
// save dates to ViewModel
// closeDialog
TODO()
}
private fun createDateFieldObserver(tw: TextInputEditText): Observer<LocalDate> {
return Observer { date ->
Log.d(TAG, "obs"+tw.id)
tw.setText(date.format(DateTimeFormatter.ISO_DATE))
//tw.setText("hello")
}
}
}
ViewModel
class TimesViewModel : ViewModel() {
val fromSampling: MutableLiveData<LocalDate> = MutableLiveData(LocalDate.now())
val toSampling: MutableLiveData<LocalDate> = MutableLiveData(LocalDate.now())
val fromSending: MutableLiveData<LocalDate> = MutableLiveData(LocalDate.now())
val toSending: MutableLiveData<LocalDate> = MutableLiveData(LocalDate.now())
val fromReceiving: MutableLiveData<LocalDate> = MutableLiveData(LocalDate.now())
val toReceiving: MutableLiveData<LocalDate> = MutableLiveData(LocalDate.now())
}
it has been a while since I programmed for android. Everything I'm using here is new to me so if you spot an anti-pattern in this little code I would be glad to know.
Tnx
Turns out it was because of the layout.
There was no enough room in the view to display the whole date.
I don't understand why it causes indexoutofbound but the solution is to simply make the view "wrap_content" or bigger