I am trying to use Data binding with LiveData in ViewModel. But when using Transformations.map functions not trigged without adding observers explicitly. Data binding in XML not generate observers for LiveData in ViewModel.
LoginFragment.kt
class LoginFragment : Fragment() {
var homeViewModel: HomeViewModel? = null
companion object {
val TAG : String = "LoginFragment"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
homeViewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val binding = DataBindingUtil.inflate<FragmentLoginBinding>(inflater, R.layout.fragment_login, container, false)
.apply {
setLifecycleOwner(this#LoginFragment)
loginButton.setOnClickListener {
signInWithEmail()
}
}
return binding.root
}
/*private fun observeHomeFragmentUIDataLiveData() {
homeViewModel?.homeFragmentUIDataLiveData?.observe(this, Observer {
val email = it.email
Toast.makeText(activity,email, Toast.LENGTH_SHORT).show()
})
}
private fun observeLoginErrorEventLiveData() {
homeViewModel?.loginErrorEventLiveData?.observe(this, Observer {
Toast.makeText(activity,it, Toast.LENGTH_SHORT).show()
})
}*/
/**
* Sign In via Email
*/
fun signInWithEmail(){
val email = email_text_input_layout.editText?.text.toString()
val password = password_text_input_layout.editText?.text.toString()
var cancel : Boolean? = false
var focusView : View? = null
if(password.isEmpty()){
password_text_input_layout.error = getString(R.string.this_field_is_required)
focusView = password_text_input_layout
cancel = true
}
if(email.isEmpty()){
email_text_input_layout.error = getString(R.string.this_field_is_required)
focusView = email_text_input_layout
cancel = true
}
if(cancel!!){
focusView?.requestFocus()
}
else{
homeViewModel?.signInWithEmail(email,password)
/*homeViewModel?.signInWithEmail(email,password)?.observe(this, Observer {
val email = it
Toast.makeText(activity,""+email, Toast.LENGTH_SHORT).show()
})*/
}
}
}
fragment_login.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable name="homeViewModel" type="com.rramprasad.adminapp.HomeViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="LoginFragment">
<!-- Skipped some code for clarity -->
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/error_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{homeViewModel.loginErrorEventLiveData.value}"
app:isGone="#{homeViewModel.isLoginSuccess}"
android:textColor="#android:color/holo_orange_dark"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/password_text_input_layout"
app:layout_constraintBottom_toBottomOf="#id/login_button"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
HomeViewModel.kt
class HomeViewModel(application: Application) : AndroidViewModel(application) {
val homeRepository: HomeRepository?
init {
homeRepository = HomeRepository()
}
// UI Data for HomeFragment
val homeFragmentUIDataLiveData : MutableLiveData<HomeFragmentUIData> = MutableLiveData()
// UI Data for LoginFragment
val loginErrorEventLiveData : MutableLiveData<String> = MutableLiveData()
var isLoginSuccess: LiveData<Boolean>? = null
fun signInWithEmail(email: String, password: String) : LiveData<Boolean>? {
val signInResponseMutableLiveData : MutableLiveData<Any> = homeRepository?.signInWithEmail(email, password)!!
isLoginSuccess = Transformations.map(signInResponseMutableLiveData) { signInResponse ->
when (signInResponse) {
(signInResponse is FirebaseUser) -> {
val firebaseUserEmail = (signInResponse as FirebaseUser).email
homeFragmentUIDataLiveData.value = HomeFragmentUIData(firebaseUserEmail ?: "")
return#map true
}
else -> {
loginErrorEventLiveData.value = signInResponse.toString()
return#map false
}
}
}
return isLoginSuccess
}
}
BindingAdapters.kt
#BindingAdapter("isGone")
fun bindIsGone(view: View, isGone: Boolean) {
view.visibility = if (isGone) {
View.GONE
} else {
View.VISIBLE
}
}
Android studio version:
Android Studio 3.2 RC 3
Build #AI-181.5540.7.32.4987877, built on August 31, 2018
JRE: 1.8.0_152-release-1136-b06 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
macOS 10.13.4
build.gradle:
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
dataBinding {
enabled = true
}
}
Related
My first time asking questions here I think I'm going insane. Working on a school project, I am creating an app like Goodreads, I have recyclerView which lists all the books from database(reading data works fine) and a different fragment where you can write a review for each book. So I have 3 layouts(main_activity layout,review layout and a layout that designs an item for recyclerView).
The problem is when I click save button which is supposed to save a review ni database it does nothing. I have a collection called "books" which has 8 documents and fields such as title,author,rating,image,review. All fields are string types. My rules allow writing and reading data. I have no idea what to do if anyone has better insight I will be really thankful
Main activity:
class MainActivity : AppCompatActivity(),BookRecyclerAdapter.ContentListener {
private lateinit var recyclerAdapter: BookRecyclerAdapter
private val db = Firebase.firestore
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(layout.activity_main)
val recyclerView = findViewById<RecyclerView>(id.booksList)
db.collection("books")
.get() //da dohvati sve dokumente
.addOnSuccessListener { //unutar ovoga pristup svim podadcima koji su se ucitali
val items: ArrayList<Book> = ArrayList()
for (data in it.documents) { //stvori novu data varijablu za svaki dohvaceni dokument(element?)
val book =
data.toObject(Book::class.java) //sve podatke pretvaramo u model preko toObject u Person
if (book != null) {
book.id = data.id //postavljanje ida
items.add(book) //dodavanje gotovog eprsona u listu
}
}
recyclerAdapter = BookRecyclerAdapter(items, this#MainActivity)
recyclerView.apply {
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = recyclerAdapter
}
}.addOnFailureListener { exception ->
Log.w("MainActivity", "Error getting documents", exception)
}
}
override fun onItemButtonClick(index: Int, item: Book, clickType: ItemClickType) {
if (clickType == ItemClickType.SAVE) {
db.collection("books").document(item.id)
.set(item)
.addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully written!") }
.addOnFailureListener { e -> Log.w(TAG, "Error writing document", e) }
}
else if (clickType == ItemClickType.REVIEW) {
val newFragment: Fragment = ReviewFragment()
val transaction: FragmentTransaction? = supportFragmentManager.beginTransaction()
transaction?.replace(R.id.container, newFragment);
transaction?.addToBackStack(null);
transaction?.commit();
}
}
}
BookRecyclerAdapter
package hr.ferit.enasalaj.zavrsni
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
enum class ItemClickType {
SAVE,
REVIEW
}
class BookRecyclerAdapter(
val items:ArrayList<Book>,
val listener: ContentListener,
): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val mainView = LayoutInflater.from(parent.context).inflate(R.layout.recycler_view_books, parent, false)
val reviewView = LayoutInflater.from(parent.context).inflate(R.layout.review, parent, false)
return BookViewHolder(mainView, reviewView)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is BookViewHolder -> {
holder.bind(position, listener, items[position])
}
}
}
override fun getItemCount(): Int {
return items.size
}
class BookViewHolder(val view: View,val review: View): RecyclerView.ViewHolder(view) {
private val bookImage =
view.findViewById<ImageView>(R.id.image)
private val bookAuthor =
view.findViewById<EditText>(R.id.bookAuthor)
private val bookTitle =
view.findViewById<EditText>(R.id.bookTitle)
private val bookRating =
view.findViewById<EditText>(R.id.bookRating)
private val reviewButton =
view.findViewById<Button>(R.id.writeReviewButton)
public val saveButton =
review.findViewById<Button>(R.id.saveButton)
public val bookReview =
review.findViewById<EditText>(R.id.saveReview)
fun bind(
index: Int,
listener: ContentListener,
item: Book,
) {
Glide.with(view.context).load(item.image).into(bookImage)
bookAuthor.setText(item.author)
bookTitle.setText(item.title)
bookRating.setText(item.rating)
bookReview.setText(item.review)
reviewButton.setOnClickListener {
listener.onItemButtonClick(index,item,ItemClickType.REVIEW)
}
saveButton.setOnClickListener{
listener.onItemButtonClick(index,item,ItemClickType.SAVE)
}
}
}
interface ContentListener {
fun onItemButtonClick(index: Int, item: Book, clickType: ItemClickType)
}
}
And here is my gradle file:
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.gms.google-services'
}
android {
namespace 'hr.ferit.enasalaj.zavrsni'
compileSdk 32
defaultConfig {
applicationId "hr.ferit.enasalaj.zavrsni"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.firebase:firebase-firestore-ktx:24.4.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
implementation 'com.github.bumptech.glide:glide:4.14.2'
implementation "com.google.firebase:firebase-auth:9.6.1"
implementation 'com.google.firebase:firebase-database:9.6.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'
apply plugin: 'com.google.gms.google-services'
}
I save the users I brought from the database in the room first. And their favorite status in the database is false. Then when I favorite it, I change its status. Every time I do this the adapter reloads. I used DiffUtil for this.
In addition, I expect the status of the adapter to remain the same after favorites on the Detail page, and if favorites are made on the detail page, it should be reflected on the adapter.
Because of the function I called from viewmodel in UserListFragment, all data is reloaded when fragment occurs.
Fragment
#AndroidEntryPoint
class UserListFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val args: UserListFragmentArgs by navArgs()
val binding = FragmentUserListBinding.inflate(inflater, container, false)
val viewModel = ViewModelProvider(this).get(UserListViewModel::class.java)
(this.activity as AppCompatActivity).supportActionBar?.title = "Github Users"
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
viewModel.userSearch(args.term) //!!!
binding.userListRecyclerView.adapter = UserListAdapter(UserListAdapter.OnClickListener {
val action = it.login.let { login ->
UserListFragmentDirections.actionUserListFragmentToUserDetailFragment(
login!!
)
}
findNavController().navigate(action)
},viewModel)
viewModel.users.observe(viewLifecycleOwner) {
bindRecyclerView(binding.userListRecyclerView, it)
}
return binding.root
}
}
ViewModel
#HiltViewModel
class UserListViewModel #Inject constructor(
private val repository: RoomRepository,
private val firestoreRepository: FirestoreRepository,
private val githubApiService: GithubApiService,
) :
ViewModel() {
private val _users = MutableLiveData<List<UserEntity>?>()
val users: LiveData<List<UserEntity>?>
get() = _users
private val userEntities: MutableList<UserEntity> = mutableListOf()
private val mutableMap: MutableMap<String?, Any?> = mutableMapOf()
fun userSearch(term: String) {
loadFromCache(term)
viewModelScope.launch {
val getPropertiesDeferred = githubApiService.searchUser(term)
try {
val result = getPropertiesDeferred.body()
result?.users?.forEach {
userEntities.add(
UserEntity(
term = term,
login = it.login,
avatar_url = it.avatar_url,
favorite = "no"
)
)
mutableMap.put(term,userEntities)
}
updateSearchResults(userEntities, term)
firestoreRepository.saveSearchResults(mutableMap,term)
} catch (e: Exception) {
Log.e("userListErr", e.message.toString())
}
}
}
fun favorite(login: String){
viewModelScope.launch {
val list = repository.getUserFavoriteStatus(login)
if (list.isNotEmpty()){
if (list[0].favorite == "no"){
repository.addFavorite(login)
}else{
repository.removeFavorite(login)
}
loadFromCache(list[0].term.toString())
}
}
}
private fun updateSearchResults(userEntities: List<UserEntity>, term: String) {
viewModelScope.launch(Dispatchers.IO) {
val favs = repository.getFavorites(term)
repository.insertSearchResults(userEntities)
if (favs.isNotEmpty()){
favs.forEach {
favorite(it.login.toString())
}
}
loadFromCache(term)
}
}
private fun loadFromCache(term: String) {
viewModelScope.launch() {
val list = repository.getSearchResults(term)
if (list.isNotEmpty()){
_users.value = list
}else{
Log.e("boş","boş dürüm")
}
}
}
}
I'm trying to build a simple app where the user enters his email address to sign up over Firebase with MVVM (Model View ViewModel).
when i try to observe the data in the view inside a fragment from the ViewModel the whole app crashes.
The error message says: Error inflating class androidx.fragment.app.FragmentContainerView
But when i remove the observing part from the View(The Login fragment the app runs correctlly)
Can someone help me to fix that problem?
the App Repository
class AppRepo {
private var application: Application? = null
val userMutableLiveData: MutableLiveData<FirebaseUser>? = null
val loggedOutMutableLiveData: MutableLiveData<Boolean>? = null
val usernameMutableLiveData: MutableLiveData<String>? = null
val uriProfilePicMutableLiveData: MutableLiveData<Uri>? = null
private var firebaseAuth: FirebaseAuth? = null
constructor(application: Application?) {
this.application = application
firebaseAuth = FirebaseAuth.getInstance()
if (firebaseAuth!!.currentUser != null) {
val userID = Objects.requireNonNull(FirebaseAuth.getInstance().currentUser)!!
.uid
userMutableLiveData!!.postValue(firebaseAuth!!.currentUser)
}
}
#RequiresApi(api = Build.VERSION_CODES.P)
fun loginUsers(email: String?, password: String?) {
firebaseAuth!!.signInWithEmailAndPassword(email!!, password!!).addOnCompleteListener(
application!!.mainExecutor,
{ task ->
if (task.isSuccessful) {
userMutableLiveData!!.postValue(firebaseAuth!!.currentUser)
} else {
Toast.makeText(
application,
"Logging in failed: " + task.exception,
Toast.LENGTH_LONG
).show()
}
})
}
#JvmName("getUserMutableLiveData1")
fun getUserMutableLiveData(): MutableLiveData<FirebaseUser>? {
return userMutableLiveData
}
}
the ViewModel
open class ViewModel(application: Application) : AndroidViewModel(application) {
private var appRepo: AppRepo? = null
private var userMutableLiveData: MutableLiveData<FirebaseUser>? = null
private var loggedOutMutableLiveData: MutableLiveData<Boolean>? = null
private var documentSnapshotMutableLiveData: MutableLiveData<String>? = null
private var uriProfilePocMutableLiveData: MutableLiveData<Uri>? = null
open fun ViewModel(application: Application) {
appRepo = AppRepo(application)
userMutableLiveData = appRepo!!.getUserMutableLiveData()
loggedOutMutableLiveData = appRepo!!.getLoggedOutMutableLiveData()
documentSnapshotMutableLiveData = appRepo!!.getUsernameMutableLiveData()
uriProfilePocMutableLiveData = appRepo!!.getUriProfilePicMutableLiveData()
}
#RequiresApi(Build.VERSION_CODES.P)
open fun login(email: String?, password: String?) {
appRepo!!.loginUsers(email, password)
}
open fun getUserMutableLiveData(): MutableLiveData<FirebaseUser>? {
return userMutableLiveData
}
open fun getUsernameMutableLiveData(): MutableLiveData<String>? {
return documentSnapshotMutableLiveData
}
open fun getUriProfilePocMutableLiveData(): MutableLiveData<Uri>? {
return uriProfilePocMutableLiveData
}
}
the LoginFragment
abstract class LoginFragment : Fragment() {
private var emailEditText: EditText? = null
private var passwordEditText:EditText? = null
private var loginButton: Button? = null
private var signupButton: Button? = null
private var resetPasswordButton:Button? = null
private var viewModel: ViewModel? = null
#RequiresApi(Build.VERSION_CODES.P)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_login, container, false)
emailEditText = view.findViewById(R.id.username)
passwordEditText = view.findViewById(R.id.password)
loginButton = view.findViewById(R.id.login)
loginButton!!.setOnClickListener(View.OnClickListener {
val email = emailEditText!!.getText().toString().trim { it <= ' ' }
val password = passwordEditText!!.getText().toString().trim { it <= ' ' }
if (email.isNotEmpty() && password.isNotEmpty()) {
viewModel!!.login(email, password)
}
})
return view
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(ViewModel::class.java)
activity?.let {
viewModel!!.getUserMutableLiveData()!!.observe(
it,
{ firebaseUser ->
if (firebaseUser != null) {
Navigation.findNavController(requireView())
.navigate(R.id.action_loginFragment3_to_mainUIFragment)
}
})
}
}
}
XML File MainActivity
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="false"
app:navGraph="#navigation/nav_graph"
tools:layout_editor_absoluteX="17dp"
tools:layout_editor_absoluteY="221dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
I have created a Sales Order menu, the menu uses the Activity and Fragment files,
The problem is when I display the Customer List page to find customer data that will be used on the SalesOrder page (Create Data tab (use Fragment))
CustomerList page not closed,
Here's my code
First run show
enter image description here
Then I clik Sales Order show like this, its file SalesOrderActivity.kt
Page SalesOrder use TabLayout and Fragment , ie (fragment for Create Data and Fragment List Data)
enter image description here
File SalesOrderActivity.kt
class SalesOrderActivity : AppCompatActivity() {
lateinit var viewPager : ViewPager
lateinit var tabs : TabLayout
private var appContext: Context? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sales_order)
CommonCtx.setViewXml2(contentView)
setSupportActionBar(findViewById(R.id.my_toolbar))
supportActionBar!!.title = "Sales Order"
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
viewPager = findViewById(R.id.viewPager)
tabs = findViewById(R.id.tabs)
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
val position = tab?.position
if (position == 1) {
println("get selected")
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
appContext = this
CommonCtx.setActivity(this)
setUpTabs()
}
//SHOW FRAGMENT CREATE DATA AND LIST DATA
private fun setUpTabs(){
val adapter = com.myapp.sfa.adapter.ViewPagerAdapter(supportFragmentManager)
adapter.addFragment(SalesOrderCreateDataFragment(), "Create Data")
adapter.addFragment(CustomerListDataFragment(), "List Data")
viewPager.adapter = adapter
tabs.setupWithViewPager(viewPager)
setupTabIcons()
}
private fun setupTabIcons() {
tabs.getTabAt(0)!!.setIcon(tabIcons.get(0))
tabs.getTabAt(1)!!.setIcon(tabIcons.get(1))
}
private val tabIcons = intArrayOf(
R.drawable.add_sign,
R.drawable.list_48
)
override fun onSupportNavigateUp(): Boolean {
finish()
return true
}
}
in page Sales Order I used fragment for show tab Create Data and List Data
this
file SalesOderCreateDataFragment.kt
class SalesOrderCreateDataFragment : Fragment() {
private var appContext: Context? = null
private lateinit var txtvCustId : TextView
public lateinit var edtCustCode : EditText
private lateinit var btnCustomer : Button
lateinit var DBHelper : DBHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appContext = this.getActivity()
DBHelper = DBHelper(appContext!!))
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_sales_order_create_data,container,false)
txtvCustId = view.findViewById<TextView>(R.id.txtvCustId)
edtCustCode = view.findViewById<EditText>(R.id.edtCustCode)
btnCustomer = view.findViewById(R.id.btnCustomer)
btnCustomer.setOnClickListener {
showCustomer()
}
appContext = this.getActivity()
return view
}
//SHOW LIST CUSTOMER event clik Cust. Code
fun showCustomer() {
val intent = Intent(this.getActivity(), CustomerListActivity::class.java)
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
CommonCtx.getContext().startActivity(intent)
CustomerListActivity().finish()
}
fun getCustomer(idCustomer: String){
var customerPojo = CustomerPojo()
customerPojo= SQLCustomer().getCustomerById(idCustomer)
}
}
while I click button Cust Code
enter image description here
Will show page CustomerListActivity, like this picture
Code in Cust code event
enter image description here
File CustomerListActivity.kt
class CustomerListActivity : AppCompatActivity() {
private lateinit var recyclerView : RecyclerView
lateinit var DBHelper : DBHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_customer_list)
setSupportActionBar(findViewById(R.id.my_toolbar))
supportActionBar!!.title = "Customer List"
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
recyclerView =findViewById(R.id.recyclerView)
DBHelper = DBHelper(applicationContext)//this)
//get data customer in database local
var listCustomerPojo : MutableList<CustomerPojo> = ArrayList()
listCustomerPojo = SQLCustomer().getCustomerList()
//show data customer in Recycle Card View
showListDataToRecycleCardView(listCustomerPojo);
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main, menu)
val searchItem = menu.findItem(R.id.menu_search)
if(searchItem != null){
val searchView = searchItem.actionView as SearchView
val editext = searchView.findViewById<EditText>
(androidx.appcompat.R.id.search_src_text)
editext.hint = "Search here..."
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
var listCustomerPojo: ArrayList<CustomerPojo> = ArrayList()
if (newText!!.isNotEmpty()) {
val search = newText.toLowerCase()
listCustomerPojo = SQLCustomer().getCustomerByName(newText)
}
showListDataToRecycleCardView(listCustomerPojo);
return true
}
})
}
return super.onCreateOptionsMenu(menu)
}
fun showListDataToRecycleCardView(listCustomerPojo: MutableList<CustomerPojo>){
var customerListDC = arrayListOf<CustomerDataModel>()
for (i in 0 until listCustomerPojo.size) {
var custPojo: CustomerPojo = listCustomerPojo[i]
val cdm = CustomerDataModel(R.drawable.shop_on_ballon_36,
custPojo.custId.toString(),
custPojo.custCode.toString(),
custPojo.custName.toString(),
custPojo.address.toString())
customerListDC.add(cdm)
}
val recyclerAdapter = CustomerListRecyclerAdapter(customerListDC)
recyclerView.apply {
adapter = recyclerAdapter
addItemDecoration(DividerItemDecoration(CommonCtx.getContext(),
DividerItemDecoration.VERTICAL))
setHasFixedSize(true)
}
}
override fun onSupportNavigateUp(): Boolean {
finish()
return true
}
}
file CustomerListRecyclerAdapter.kt
class CustomerListRecyclerAdapter(private var customerListData: ArrayList<CustomerDataModel>)
: RecyclerView.Adapter<CustomerListRecyclerAdapter.ViewHolder>(){
var appContext: Context? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.recycler_customer_list, parent, false)
appContext = parent.context
return ViewHolder(view)
}
override fun getItemCount(): Int = customerListData.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val movie = customerListData[position]
holder.bind(movie)
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val intent: Intent? = null
init {
itemView.setOnClickListener {
var textvCustId = itemView.findViewById<TextView>(R.id.textvCustId)
var textvCustName = itemView.findViewById<TextView>(R.id.textvCustName)
}
}
fun bind(model: CustomerDataModel) {
var imgViewIcon = itemView.findViewById<ImageView>(R.id.imgViewIcon)
var textvCustId = itemView.findViewById<TextView>(R.id.textvCustId)
var textvCustCode = itemView.findViewById<TextView>(R.id.textvCustCode)
var textvCustName = itemView.findViewById<TextView>(R.id.textvCustName)
var textvAddress = itemView.findViewById<TextView>(R.id.textvAddress)
var btnCreateSO = itemView.findViewById<Button>(R.id.btnCreateSO)
var btnCreateSR = itemView.findViewById<Button>(R.id.btnCreateSR)
imgViewIcon.setImageResource(model.iconPict)
textvCustId.text = model.custId
textvCustCode.text = model.custCode
textvCustName.text = model.custName
textvAddress.text = model.address
textvCustId.setVisibility(View.INVISIBLE)
btnCreateSO.setOnClickListener {
SalesOrderCreateDataFragment().getCustomer(textvCustId.text.toString())
}
}
}//end class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
File CommonCtx.kt
#SuppressLint("StaticFieldLeak")
abstract class CommonCtx {
companion object {
#Volatile
private lateinit var appContext : Context
private lateinit var mainActivity : Activity
private lateinit var myActivity : Activity
private lateinit var myView : View
private lateinit var myView2 : View
private lateinit var myView3 : View
private lateinit var myContainer: ViewGroup
fun setContext(context: Context) {
appContext = context
}
fun getFilesDir():String {
return appContext.filesDir.absolutePath
}
fun getOpenFileInput(fileName : String): FileInputStream {
return appContext.openFileInput(fileName)
}
fun getOpenFileOutput(fileName : String): FileOutputStream {
return appContext.openFileOutput(fileName, MODE_PRIVATE)
}
fun getContext():Context{
return appContext
}
fun getContext2():Context{
return appContext.applicationContext
}
fun setMainActivity(activity: Activity) {
mainActivity = activity
}
fun getMainActivity():Activity{
return mainActivity
}
fun setActivity(activity: Activity) {
myActivity = activity
}
fun getActivity():Activity{
return myActivity
}
fun setViewXml(view : View) {
myView = view
}
fun getViewXml():View{
return myView
}
fun setViewXml2(view: View?) {
if (view != null) {
myView2 = view
}
}
fun getViewXml2():View{
return myView2
}
fun setViewXml3(view: View?) {
if (view != null) {
myView3 = view
}
}
fun getViewXml3():View{
return myView3
}
fun setContainer(container : ViewGroup) {
myContainer = container
}
fun getContainer():ViewGroup{
return myContainer
}
}
}
File AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myapp.sfa">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:networkSecurityConfig="#xml/network_security_config"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.MyAppSFA">
<activity android:name=".view.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.view.NetworkSettingActivity"
android:screenOrientation="portrait" />
<activity android:name=".ui.view.salesman.SalesmanProfileActivity"
android:screenOrientation="portrait" />
<activity android:name=".ui.view.sales.SalesOrderActivity"
android:launchMode="singleTop"/>
<activity android:name=".ui.view.sales.SalesReturnActivity"/>
<activity android:name=".view.ui.customer.CustomerActivity" />
<activity android:name=".view.ui.list.CustomerListActivity"
android:noHistory="true"/>
<activity android:name=".view.ui.list.ProductItemListActivity" />
</application>
</manifest>
Problem , while I click button Create SO in page CustomerListActivity
Page CustomerListActivity is not Closed and back to Page SalesOrderActivity
Thanks
enter image description here
in this code, file CustomerListRecyclerAdapter.kt
is not working, CustomerListActivity.kt not close
btnCreateSO.setOnClickListener {
SalesOrderCreateDataFragment().getCustomer(textvCustId.text.toString())
}
this is my file build.gradle(:app)
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.myapp.sfa"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
dataBinding {
enabled = true
}
sourceSets {
main {
res.srcDirs = [
'src/main/res',
'src/main/res/layout',
'src/main/res/layout/fragments',
'src/main/res/layout/salesaction',
'src/main/res/layout/salesman',
'src/main/res/layout/setting',
'src\\main\\res', 'src\\main\\res\\layout\\itemrecycler', 'src\\main\\res\\layout\\product', 'src\\main\\res\\layout\\customer'
]
}
//debug {
// res.srcDirs = ['resources/debug']
//}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.anko:anko:0.10.8"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
implementation "com.squareup.retrofit2:converter-moshi:2.4.0"
implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'androidx.lifecycle:lifecycle-common:2.2.0'
implementation 'androidx.lifecycle:lifecycle-runtime:2.2.0'
implementation 'android.arch.lifecycle:extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
implementation "androidx.viewpager:viewpager:1.0.0"
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.appcompat:appcompat:1.3.0-rc01'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation "org.jetbrains.kotlin:kotlin-test-
junit:$kotlin_version"
}
any other ideas, should i change the way to display the Customer list?
Thanks
i have add code (itemView.context as Activity).finish() in CustomerListRecycleAdapter.kt
btnCreateSO.setOnClickListener {
SalesOrderActivity().selectedTabForUpdate(textvCustId.text.toString())
(itemView.context as Activity).finish()
}
and this work, Customer List close
Thanks all
My goal is to use MapBox and connect the view to the data set containing GeoJSON polygons. I am already able to get changes from the dataset through a LiveData<List> where Case among other things contains an GeoJSON area.
Now I want to listen to changes to this data set from the ViewModel and bind the results to a specific layer in the map. I haven't been able to find any examples on how to do this as most samples does not use data binding or ViewModel with LiveData.
data class Case (
var id : String,
var feature : Feature,
var note : String?
)
Note: The Feature is a MapBox.Feature that implements GeoJSON and it is a polygon.
I am already able to listen for the changes on the list of cases but haven't figured out how to connect the remaining pieces. Can you help me on the steps here or maybe point to a good example for this? A possible answer could be some explanations along with some pheudo code.
<com.mapbox.mapboxsdk.maps.MapView
android:id="#+id/MyMapView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/jobSelectionJobTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:mapbox_cameraZoom="12" />
And a bonus question: I am not sure how much to put in the activity and what to put into the ModelView.
This example uses :
Data Binding
Binding Adapters
LiveData
Kotlin Coroutines
com.mapbox.mapboxsdk.style.sources.GeoJsonSource
com.mapbox.mapboxsdk.style.layers.Layer
The aim to bind GeoJsonSource and Layer objects to the MapView via databinding and LiveData. Also provide a mechanism to remove layers by their "id". I have deliberably left implementation of LiveData blank as I have no means of how this data is supplied, but provide the mechanism by which this can be achieved. This mechanism can be adapted to suit your needs.
Data Classes :
data class GeoJsonLayer(val source: GeoJsonSource, val layer: Layer)
Binding Adapters (place in separate kotlin file for global access)
#BindingAdapter(value = ["addGeoJsonLayers", "addCoroutineScope"], requireAll = true)
fun MapView.addGeoJsonLayers(layers: List<GeoJsonLayer>?, scope: CoroutineScope?) {
layers?.let { list ->
scope?.launch {
getStyle()?.run {
list.filter { getSource(it.source.id) == null }
.forEach { jsonLayer ->
addSource(jsonLayer.source)
addLayer(jsonLayer.layer)
}
}
}
}
}
#BindingAdapter(value = ["removeLayers", "removeCoroutineScope"], requireAll = true)
fun MapView.removeLayers(ids: List<String>?, scope: CoroutineScope?) {
ids?.let { list ->
scope?.launch {
getStyle()?.run {
list.forEach { id ->
removeLayer(id)
removeSource(id)
}
}
}
}
}
Coroutine Extension Functions (place in separate kotlin file for global access)
suspend fun MapView.getMap(): MapboxMap = suspendCoroutine { cont -> getMapAsync { cont.resume(it) } }
suspend fun MapView.getStyle(): Style? = getMap().style
Example ViewModel contract
abstract class MapViewModel : ViewModel() {
abstract val addGeoJsonLayers: LiveData<List<GeoJsonLayer>>
abstract val removeGeoJsonLayers: LiveData<List<String>>
}
XML Layout (layout/map_view.xml)
<data>
<import type="android.view.View" />
<variable
name="mapVm"
type="your.package.MapViewModel" />
<variable
name="scope"
type="kotlinx.coroutines.CoroutineScope" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mapbox.mapboxsdk.maps.MapView
android:id="#+id/map_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:addCoroutineScope="#{scope}"
app:addGeoJsonLayers="#{mapVm.addGeoJsonLayers}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:removeCoroutineScope="#{scope}"
app:removeLayers="#{mapVm.removeGeoJsonLayers}" />
</androidx.constraintlayout.widget.ConstraintLayout>
Example Activity
class MapActivity : AppCompatActivity() {
private lateinit var binding: MapViewBinding
private val viewModel: ConcreteMapViewModel by viewModels() // implementation of MapViewModel
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
binding = MapViewBinding.inflate(layoutInflater).apply {
setContentView(root)
lifecycleOwner = lifecycleOwner
scope = lifecycleOwner.lifecycleScope
mapView.onCreate(savedInstanceState)
lifecycleOwner.lifecycleScope.launch {
mapView.getMap().setStyle(Style.MAPBOX_STREETS)
}
mapVm = viewModel
}
}
override fun onStart() {
super.onStart()
binding.mapView.onStart()
}
override fun onResume() {
super.onResume()
binding.mapView.onResume()
}
override fun onPause() {
super.onPause()
binding.mapView.onPause()
}
override fun onStop() {
super.onStop()
binding.mapView.onStop()
}
override fun onDestroy() {
super.onDestroy()
binding.mapView.onDestroy()
}
override fun onLowMemory() {
super.onLowMemory()
binding.mapView.onLowMemory()
}
}
Gradle file setup (kts - kotlin DSL) :
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt")
}
android {
compileSdk = 30
buildToolsVersion = "30.0.3"
defaultConfig {
applicationId = "com.package.name"
minSdk = 24
targetSdk = 30
versionCode = 1
versionName = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
named("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
dataBinding = true
}
}
dependencies {
// Kotlin
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.5.10")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0")
// AndroidX
val lifecycleVersion = "2.3.1"
val navVersion = "2.3.5"
implementation("androidx.core:core-ktx:1.5.0")
implementation("androidx.appcompat:appcompat:1.3.0")
implementation("androidx.constraintlayout:constraintlayout:2.0.4")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion")
// MapBox
implementation("com.mapbox.mapboxsdk:mapbox-android-sdk:9.6.1")
implementation("com.google.code.gson:gson:2.8.7")
// Logging
implementation("com.jakewharton.timber:timber:4.7.1")
testImplementation("junit:junit:4.13.2")
}