im new to making app and trying to make a task list app using android studio, firebase, and kotlin. In MainActivity, i show the list of task that stored in fire base. then there is button to add a new task and store it into firebase. if i click on a task at MainActivity, the app will move to EditTaskActivity, which contain some EditText element, and if user fill that EditText, then press button "Save Change" in the EditTaskActivity, it supposed to store the data change in firebase. But the problem is, when i press the "Save Change" button, the data in firebase keep changing forever between the old data and new data, and make the app can't stopped working.
This is my MainActivity code:
class MainActivity : AppCompatActivity() {
private lateinit var titlePage: TextView
private lateinit var subtitlePage: TextView
private lateinit var endPage: TextView
private lateinit var rvTasks: RecyclerView
private var list: ArrayList<Task> = arrayListOf()
private lateinit var reference: DatabaseReference
private lateinit var btnAddNew: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
titlePage=findViewById(R.id.title_page)
subtitlePage=findViewById(R.id.subtitle_page)
endPage=findViewById(R.id.end_page)
btnAddNew=findViewById(R.id.btn_addnew)
rvTasks = findViewById(R.id.rv_taskList)
rvTasks.setHasFixedSize(true)
rvTasks.layoutManager = LinearLayoutManager(this)
reference = FirebaseDatabase.getInstance().reference.child("TaskListApp")
reference.addValueEventListener( object: ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot){
for(dataSnapshot1 in dataSnapshot.children){
var task: Task? = dataSnapshot1.getValue<Task>(Task::class.java)
if (task != null) {
list.add(task)
}
}
val taskAdapter = TaskAdapter(list,assets)
rvTasks.adapter = taskAdapter
taskAdapter.notifyDataSetChanged()
}
override fun onCancelled(databaseError: DatabaseError){
Toast.makeText(applicationContext, "No Data", Toast.LENGTH_SHORT).show()
}
} )
btnAddNew.setOnClickListener { v ->
if(v.id == R.id.btn_addnew){
val moveToAddNew = Intent(this#MainActivity, AddNewTask::class.java)
startActivity(moveToAddNew)
}
}
}
}
This is My TaskAdapter code:
class TaskAdapter(private val taskList: ArrayList<Task>, assets: AssetManager): RecyclerView.Adapter<TaskAdapter.ListViewHolder>() {
lateinit var someContext: Context
inner class ListViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
var taskTitle: TextView = itemView.findViewById(R.id.task_title)
var taskDesc: TextView = itemView.findViewById(R.id.task_desc)
var taskDate: TextView = itemView.findViewById(R.id.task_date)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
val view: View = LayoutInflater.from(parent.context).inflate(R.layout.item_tasks, parent, false)
someContext = parent.context
return ListViewHolder(view)
}
override fun getItemCount(): Int {
return taskList.size
}
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
val task = taskList[position]
holder.taskTitle.text = task.taskTitle
holder.taskDesc.text = task.taskDesc
holder.taskDate.text = task.taskDate
holder.itemView.setOnClickListener {
val moveToEdit = Intent(someContext, EditTask::class.java)
moveToEdit.putExtra("tasktitle", task.taskTitle)
moveToEdit.putExtra("taskdesc", task.taskDesc)
moveToEdit.putExtra("taskdate", task.taskDate)
moveToEdit.putExtra("taskkey", task.taskKey)
someContext.startActivity(moveToEdit)
}
}
}
and this is my EditTaskActivity code:
class EditTask : AppCompatActivity() {
private lateinit var pageTitle: TextView
private lateinit var addNewTaskTitle: TextView
private lateinit var inputTaskTitle: EditText
private lateinit var addNewTaskDesc: TextView
private lateinit var inputNewTaskDesc: EditText
private lateinit var addNewTaskDueDate: TextView
private lateinit var inputNewTaskDueDate: EditText
private lateinit var btnSaveChange: Button
private lateinit var btnDeleteTask: Button
private lateinit var btnCancel: Button
private lateinit var reference: DatabaseReference
private lateinit var taskKey: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_task)
taskKey = intent?.getStringExtra("taskkey").toString()
pageTitle=findViewById(R.id.add_new_task_title_page)
pageTitle.typeface=CircularBlack
addNewTaskTitle=findViewById(R.id.new_task_title)
addNewTaskTitle.typeface=CircularMedium
inputTaskTitle=findViewById(R.id.add_task_title)
inputTaskTitle.typeface=CircularBook
inputTaskTitle.setText(intent.getStringExtra("tasktitle"))
addNewTaskDesc=findViewById(R.id.new_task_desc)
addNewTaskDesc.typeface=CircularMedium
inputNewTaskDesc=findViewById(R.id.add_task_desc)
inputNewTaskDesc.typeface=CircularBook
inputNewTaskDesc.setText(intent.getStringExtra("taskdesc"))
addNewTaskDueDate=findViewById(R.id.new_task_time)
addNewTaskDueDate.typeface=CircularMedium
inputNewTaskDueDate=findViewById(R.id.add_task_time)
inputNewTaskDueDate.setText(intent.getStringExtra("taskdate"))
reference = FirebaseDatabase.getInstance().reference.child("TaskListApp").child("Task$taskKey")
btnSaveChange=findViewById(R.id.btn_save_change)
btnSaveChange.setOnClickListener{ v ->
if(v.id==R.id.btn_save_change) {
reference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
dataSnapshot.ref.child("taskTitle").setValue(inputTaskTitle.text.toString())
dataSnapshot.ref.child("taskDesc").setValue(inputNewTaskDesc.text.toString())
dataSnapshot.ref.child("taskDate").setValue(inputNewTaskDueDate.text.toString())
startActivity(Intent(this#EditTask, MainActivity::class.java))
}
override fun onCancelled(databaseError: DatabaseError) {
Toast.makeText(
applicationContext,
"Action Cancelled, Error Occured",
Toast.LENGTH_SHORT
).show()
startActivity(Intent(this#EditTask, MainActivity::class.java))
}
})
}
}
btnDeleteTask=findViewById(R.id.btn_delete_task)
btnDeleteTask.setOnClickListener{v ->
if(v.id==R.id.btn_delete_task){
reference.removeValue().addOnCompleteListener {
if(it.isSuccessful){
startActivity(Intent(this#EditTask, MainActivity::class.java))
} else {
Toast.makeText(applicationContext, "Failed to delete task", Toast.LENGTH_SHORT).show()
}
}
}
}
btnCancel=findViewById(R.id.btn_cancel_edit)
btnCancel.setOnClickListener{ v ->
if(v.id == R.id.btn_cancel_edit) {
startActivity(Intent(this#EditTask, MainActivity::class.java))
}
}
}
}
and this is my AddNewTask activity code:
class AddNewTask : AppCompatActivity(), View.OnClickListener {
private lateinit var pageTitle: TextView
private lateinit var addNewTaskTitle: TextView
private lateinit var inputTaskTitle: EditText
private lateinit var addNewTaskDesc: TextView
private lateinit var inputNewTaskDesc: EditText
private lateinit var addNewTaskDueDate: TextView
private lateinit var inputNewTaskDueDate: EditText
private lateinit var btnCreateTask: Button
private lateinit var btnCancel: Button
private lateinit var reference: DatabaseReference
var taskNum: Int = Random.nextInt()
private var taskKey: String = taskNum.toString()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_new_task)
pageTitle=findViewById(R.id.add_new_task_title_page)
pageTitle.typeface=CircularBlack
addNewTaskTitle=findViewById(R.id.new_task_title)
inputTaskTitle=findViewById(R.id.add_task_title)
addNewTaskDesc=findViewById(R.id.new_task_desc)
inputNewTaskDesc=findViewById(R.id.add_task_desc)
addNewTaskDueDate=findViewById(R.id.new_task_time)
inputNewTaskDueDate=findViewById(R.id.add_task_time)
btnCreateTask=findViewById(R.id.btn_save_task)
btnCreateTask.setOnClickListener(this)
btnCancel=findViewById(R.id.btn_cancel)
btnCancel.setOnClickListener(this)
}
override fun onClick(v: View) {
when(v.id){
R.id.btn_save_task -> {
reference = FirebaseDatabase.getInstance().reference.child("TaskListApp").
child("Task$taskNum")
reference.addValueEventListener(object: ValueEventListener{
override fun onDataChange(dataSnapshot: DataSnapshot) {
dataSnapshot.ref.child("taskTitle").setValue(inputTaskTitle.text.toString())
dataSnapshot.ref.child("taskDesc").setValue(inputNewTaskDesc.text.toString())
dataSnapshot.ref.child("taskDate").setValue(inputNewTaskDueDate.text.toString())
dataSnapshot.ref.child("taskKey").setValue(taskKey)
startActivity(Intent(this#AddNewTask, MainActivity::class.java))
}
override fun onCancelled(databaseError: DatabaseError) {
Toast.makeText(applicationContext, "Action Cancelled", Toast.LENGTH_SHORT).show()
startActivity(Intent(this#AddNewTask, MainActivity::class.java))
}
})
}
R.id.btn_cancel -> {
startActivity(Intent(this#AddNewTask, MainActivity::class.java))
}
}
}
}
Try to use single value event listener instead of value event listener
Use
reference.addListenerForSingleValueEvent
Instead of
reference.addValueEventListener
Because value event listener is call all the time when data changed into Firebase child but single value event listener is only call one time. So, maybe it was generated the issue for you.
you can use ChildEventListener also with ValueEventListener.
Value listener will trigger method every time when data are changed in db and return all data
ChildEventListener also will will trigger method every time when data are changed in db but it's will returned item which is changes.
ChildEventListener listener= new ChildEventListener(){
onCancelled(DatabaseError error) {
// This method will be triggered in the event that this listener either failed at the server, or is removed as a result of the security and Firebase rules.
}
onChildAdded(DataSnapshot snapshot, String previousChildName){
// triggered when a new child is added to the location to which this listener was added.
//check and add new item to your adapter here
}
onChildChanged(DataSnapshot snapshot, String previousChildName){
// triggered when the data at a child location has changed.
}
onChildMoved(DataSnapshot snapshot, String previousChildName) {
// triggered when a child location's priority changes.
}
onChildRemoved(DataSnapshot snapshot) {
// triggered when a child is removed from the location to which this listener was added.
}
}
Then remove your value listener after read all items and add child listener to observe new items and changes
reference.addValueEventListener( object: ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot){
for(dataSnapshot1 in dataSnapshot.children){
var task: Task? = dataSnapshot1.getValue<Task>(Task::class.java)
if (task != null) {
list.add(task)
}
}
val taskAdapter = TaskAdapter(list,assets)
rvTasks.adapter = taskAdapter
taskAdapter.notifyDataSetChanged()
reference.removeListener(this);
reference.addValueEventListener(listener);
}
Related
I'm learning Kotlin from Java, and I'm trying to figure out how to create and store the ViewModel. Storing it outside the main activity will cause it to crash, but storing it inside the main activity means my private variables cannot access them, so I get an error.
Here's the code I have
class MainActivity : AppCompatActivity() {
private lateinit var trueButton: Button
private lateinit var falseButton: Button
private lateinit var nextButton: Button
private lateinit var prevButton: Button
private lateinit var questionTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val quizViewModel: QuizViewModel = ViewModelProvider(this).get(QuizViewModel::class.java)
trueButton = findViewById(R.id.true_button)
falseButton = findViewById(R.id.false_button)
nextButton = findViewById(R.id.next_button)
prevButton = findViewById(R.id.back_button)
questionTextView = findViewById(R.id.question_text)
trueButton.setOnClickListener {
checkAnswer(true)
}
falseButton.setOnClickListener {
checkAnswer(false)
}
nextButton.setOnClickListener {
quizViewModel.moveToNext()
updateQuestion()
}
prevButton.setOnClickListener {
quizViewModel.moveToBack()
updateQuestion()
}
updateQuestion()
}
private fun checkAnswer(userAnswer: Boolean){
val correctAnswer = quizViewModel.currentQuestionAnswer
val messageResId = if (userAnswer == correctAnswer) {
R.string.correct_toast
} else {
R.string.incorrect_toast
}
Toast.makeText(
this,
messageResId,
Toast.LENGTH_SHORT
).show()
}
private fun updateQuestion() {
val questionTextResID = quizViewModel.currentQuestionText
questionTextView.setText(questionTextResID)
}
}
Unfortunately, putting the ViewModel inside my onCreate means I can't access them in my functions, namely checkAnswer and updateQuestion.
import androidx.lifecycle.ViewModel
class QuizViewModel : ViewModel() {
private val questionBank = listOf(
Question(R.string.question_aus, true),
Question(R.string.question_ocean, true),
Question(R.string.question_mideast, false),
Question(R.string.question_africa, false),
Question(R.string.question_america, true),
Question(R.string.question_asia, true)
)
var currentIndex = 0
val currentQuestionAnswer: Boolean
get() = questionBank[currentIndex].answer
val currentQuestionText: Int
get() = questionBank[currentIndex].textResId
fun moveToNext(){
currentIndex = (currentIndex + 1) % questionBank.size
}
fun moveToBack(){
currentIndex = (currentIndex - 1) % questionBank.size
}
}
And my error
e: C:\Users\Jacob\AndroidStudioProjects\GeoQuiz\app\src\main\java\com\jacobjumper\geoquiz\MainActivity.kt: (54, 29): Unresolved reference: quizViewModel
just move the quizViewModel to the rest of the variables.
so like
class MainActivity : AppCompatActivity() {
private lateinit var trueButton: Button
private lateinit var falseButton: Button
private lateinit var nextButton: Button
private lateinit var prevButton: Button
private lateinit var questionTextView: TextView
private lateinit var quizViewModel: QuizViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
quizViewModel = ViewModelProvider(this).get(QuizViewModel::class.java)
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
}
}
})
}
}
I have framelayout in activity and two fragments that I need to attach to the activity depending on the user's choise. When I attach one fragment and change orientation, my fragment gest destrotroyed with activity. How to keep the state of fragment inside activity?
This is my menuActivity which has framelayout.
class MenuActivity : AppCompatActivity(), OptionsFragment.PassFragment {
private val TAG = "Menu"
private lateinit var binding: ActivityMenuBinding
private lateinit var informationViewModel: InformationViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMenuBinding.inflate(layoutInflater)
setContentView(binding.root)
informationViewModel = ViewModelProvider(this).get(InformationViewModel::class.java)
Log.i(TAG, "onCreate: code: ${informationViewModel.hashCode()}")
if (informationViewModel.fragment != null) {
showFragment(informationViewModel.fragment!!)
} else {
showFragment(OptionsFragment())
}
}
private fun showFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction().replace(R.id.container, fragment)
.commit()
}
override fun onPassFragment(fragment: Fragment) {
informationViewModel.fragment = fragment
}
}
This is my first fragment which should appear first in activity and it has two textviews for two fragments.
class OptionsFragment : Fragment(R.layout.options_fragment) {
private lateinit var txtPersonal: TextView
private lateinit var txtSecond: TextView
private lateinit var informationViewModel: InformationViewModel
private lateinit var passFragment: PassFragment
private val TAG = "OptionsFragment"
interface PassFragment {
fun onPassFragment(fragment: Fragment)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
txtPersonal = view.findViewById(R.id.person_det)
txtSecond = view.findViewById(R.id.trading_currency)
informationViewModel = ViewModelProvider(this).get(InformationViewModel::class.java)
Log.i(TAG, "onViewCreated: code: ${informationViewModel.hashCode()} ")
txtPersonal.setOnClickListener {
passFragment(PersonalInfFragment())
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.container, PersonalInfFragment()).commit()
}
txtSecond.setOnClickListener {
passFragment(TradingFragment())
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.container, TradingFragment()).commit()
}
}
private fun passFragment(fragment: Fragment) {
try {
passFragment = activity as PassFragment
passFragment.onPassFragment(fragment)
} catch (ex: ClassCastException) {
}
}
}
Let's say from above options fragment, I opened my second TradingFragment and showed some information from retrofit using the same viewmodel.
class TradingFragment : Fragment(R.layout.currency_trading) {
private val TAG = "TradingFragment"
private lateinit var informationViewModel: InformationViewModel
private lateinit var rec_view: RecyclerView
private lateinit var btnSearch: Button
private lateinit var adapter: TradingAdapter
private lateinit var rangeSeek: RangeSlider
private lateinit var txtStartDate: TextView
private lateinit var txtFinishData: TextView
private lateinit var spinner: Spinner
private var from = 0
private var to = 0
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnSearch = view.findViewById(R.id.btnSearch)
rec_view = view.findViewById(R.id.rec_view)
rangeSeek = view.findViewById(R.id.rangeSlider)
txtStartDate = view.findViewById(R.id.txtFrom)
txtFinishData = view.findViewById(R.id.txtTo)
spinner = view.findViewById(R.id.spinner)
informationViewModel = ViewModelProvider(this).get(InformationViewModel::class.java)
val cur_time = "${System.currentTimeMillis()}".substring(0, 10)
rangeSeek.valueTo = Integer.valueOf(cur_time) * 1.0f
initRecyclerView()
rangeSeek.addOnSliderTouchListener(object : RangeSlider.OnSliderTouchListener {
override fun onStartTrackingTouch(slider: RangeSlider) {
}
override fun onStopTrackingTouch(slider: RangeSlider) {
changeDateValueForText(slider.values.get(0).toInt(), slider.values.get(1).toInt())
}
})
btnSearch.setOnClickListener {
startSearchingCurrencyTrading()
}
}
private fun startSearchingCurrencyTrading() {
val pair = spinner.selectedItem.toString()
informationViewModel.startRequestingCurrency(pair, from, to)
}
private fun changeDateValueForText(start: Int, finish: Int) {
from = start
to = finish
val startDate = Date(start * 1000L)
val finishDate = Date(finish * 1000L)
val df = SimpleDateFormat("dd:MM:yyyy")
txtStartDate.text = df.format(startDate)
txtFinishData.text = df.format(finishDate)
}
private fun initRecyclerView() {
adapter = TradingAdapter()
rec_view.adapter = adapter
rec_view.layoutManager = LinearLayoutManager(requireActivity())
informationViewModel.returnCurrentTrading().observe(this) {
if (it != null) {
adapter.updateTradingItems(it)
} else {
adapter.updateTradingItems(listOf())
}
}
}
}
This is my current solution it worked, but I lost my previous query result to retrofit that was being showed on the tradingfragment before orientation change.
Rotation of the screen is defined a configuration change and If you want to preserve the state of your UI I suggest you to implent a ViewModel as described here
However, you have several options to preserve the state of the UI, the choose depends on your needs, all possible scenarios are well explained in the official documentation.
I am new in Mobile Development. I am trying to pass parameters from one Activity to another with intent. But in the second activity I cannot use it because of the Type miss-match. I get it as String but I should use it as TextView. Can you please say where I did mistake?
First Activity
class ShowProfileActivity : AppCompatActivity() {
private lateinit var fullnameTv: TextView
private lateinit var nicknameTv: TextView
private lateinit var locationTv: TextView
private lateinit var emailTv: TextView
private lateinit var bioTv: TextView
// todo load from sp
private val mockUser: User? = User("test ", "test", "test#gmail.com", "Italy", "test, this is a mocked bio")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_show_profile)
fullnameTv = findViewById(R.id.asp_user_name_tv)
nicknameTv = findViewById(R.id.asp_user_nickname_tv)
locationTv = findViewById(R.id.asp_user_location_tv)
emailTv = findViewById(R.id.asp_user_email_tv)
bioTv = findViewById(R.id.asp_user_bio_tv)
// init views with user data
fullnameTv.text = mockUser?.fullname
nicknameTv.text = mockUser?.nickname
locationTv.text = mockUser?.location
emailTv.text = mockUser?.email
bioTv.text = mockUser?.bio
var editButton = findViewById<ImageButton>(R.id.editBtn);
editButton.setOnClickListener {
editProfile();
}
}
fun editProfile() {
val intent = Intent(this#ShowProfileActivity,EditProfileActivity::class.java)
if (mockUser != null) {
intent.putExtra("group23.lab1.user_fullname", mockUser.fullname)
intent.putExtra("group23.lab1.user_nickname", mockUser.nickname)
intent.putExtra("group23.lab1.user_location", mockUser.location)
intent.putExtra("group23.lab1.user_email", mockUser.email)
intent.putExtra("group23.lab1.user_bio", mockUser.bio)
}
startActivity(intent)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.show_profile_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_show_profile_edit -> {
/*
TODO start edit profile activity (check activity name)
val intent = Intent(this, com.group23.lab1.EditProfileActivity::class.java).apply {
if (mockUser != null) {
putExtra("group23.lab1.user_fullname", mockUser.fullname)
putExtra("group23.lab1.user_nickname", mockUser.nickname)
putExtra("group23.lab1.user_location", mockUser.location)
putExtra("group23.lab1.user_email", mockUser.email)
putExtra("group23.lab1.user_bio", mockUser.bio)
}
}
startActivityForResult(intent)
*/
Toast.makeText(this, "OPEN EDIT PROFILE", Toast.LENGTH_SHORT).show()
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
Second Activity
class EditProfileActivity : AppCompatActivity() {
private lateinit var fullnameTv: TextView
private lateinit var nicknameTv: TextView
private lateinit var locationTv: TextView
private lateinit var emailTv: TextView
private lateinit var bioTv: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_profile)
val intent = intent
fullnameTv = intent.getStringExtra("group23.lab1.user_fullname") //Type miss match error required textView but got String ??
print(fullnameTv+" testt");
}
}
You are passing a string from FirstActivity to your SecondActivity which is the correct approach.
In your SecondActivity receive that string and set it in your TextView.
val fullName = intent.getStringExtra("group23.lab1.user_fullname")
fullnameTv.text = fullName
You define fullnameTv as TextView, but the intent extra contains string, that's why the two types are mismatched.
What you want to do is to set the textview's text property - fullnameTv.text = intent.getStringExtra("group23.lab1.user_fullname")
I am trying to execute a method(fun onCreateViewModel()) in viewModel whenever the onCreate of the Activity is invoked. But the method is not getting executed.
ViewModel
class MainActivityViewModel(startingCount : Int) : ViewModel(), LifecycleObserver {
var count = 0
init {
count = startingCount
}
fun getCurrentCount(): Int{
return count
}
fun getUpdatedCount(): Int {
count ++
return count
}
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public fun onCreateViewModel(){
Log.i("Jts"," ViewModel created")
}
}
Activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var user = User("Jeffin T", "26")
private lateinit var mainActivityViewModel: MainActivityViewModel
private lateinit var mainActivityViewModelFactory: MainActivityViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// mainActivityViewModel = ViewModelProviders.of()
mainActivityViewModelFactory = MainActivityViewModelFactory(123)
mainActivityViewModel = ViewModelProviders.of(this, mainActivityViewModelFactory)
.get(MainActivityViewModel::class.java)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.tvName.text = mainActivityViewModel.getCurrentCount().toString()
binding.user = user
binding.btnName.setOnClickListener {
binding.tvName.text = mainActivityViewModel.getUpdatedCount().toString()
}
}
private fun displayGreeting() {
binding.apply { tvName.setText("Hello! " + user?.name) }
}
}
You have to add the viewmodel as an observer of the lifecycle of your activity. You need to add this in your Activity
getLifecycle().addObserver(viewModel)
More info here