I'm working on a news app and I want my layout to become invisible when a user saves an article. Can anyone please tell me the right way to write this code?
2 things to note:
-when I run the app,the layout is visible in the "saved fragment"
but then when I add "hideSavedMessage" right next to the code that updates the recyclerView and I run the app, the layout becomes invisible.
I want the layout to be invisible only when the user saves an article.
PS: I know how the visible and invisible mode works. I have used it before. My major problem is not knowing the right place to write the code. And by layout, I mean the text view and image view that appears on the screen. I would appreciate any contributions. Thank you.
Here's my code
class SavedFragment : Fragment(R.layout.fragment_saved) {
lateinit var viewModel: NewsViewModel
lateinit var newsAdapter: SavedAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = (activity as NewsActivity).viewModel
setupRecyclerView()
newsAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("article", it)
}
findNavController().navigate(
R.id.action_savedFragment_to_savedArticleFragment,
bundle
)
}
val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position =
viewHolder.adapterPosition//get position of item we deleted so that we swipe to left or right
val article =
newsAdapter.differ.currentList[position]//from news adapter at the index of the position
viewModel.deleteArticle(article)
Snackbar.make(view, "Successfully deleted article", Snackbar.LENGTH_LONG)
.apply {
setAction("Undo") {
viewModel.saveArticle(article); hideSavedMessage()
}
show()
}
val isAtLastItem = position <= 0
val shouldUpdateLayout = isAtLastItem
if (shouldUpdateLayout) {
showSavedMessage()
}
}
}
ItemTouchHelper(itemTouchHelperCallback).apply {
attachToRecyclerView(rvSavedNews)
}
viewModel.getSavedNews().observe(viewLifecycleOwner, Observer { articles ->
newsAdapter.differ.submitList(articles)
})
}
private fun setupRecyclerView() {
newsAdapter = SavedAdapter()
rvSavedNews.apply {
adapter = newsAdapter
layoutManager = LinearLayoutManager(activity)
}
}
private fun hideSavedMessage() {
savedMessage.visibility = View.INVISIBLE
isArticleAdded = false
}
private fun showSavedMessage() {
savedMessage.visibility = View.VISIBLE
}
}
The problem is that code inside observer runs even at the beginning - when you run your app, right? If I understand your problem, you just have to manage to make the fun hideSavedMessage() not be running for the first time. You could for example instantiate a boolean in onCreate() and set it to false. Then, inside the observer, you could run the hideSavedMessage() only if that boolean is true - you would set it as true at the end of the observer. I hope you understand.
Related
I'm new here and I'm new to programming in general.
I followed a YouTube video to make a simple todo list app for android and have since modified it into a visitors register.
I am copying people who sign in to an external txt file (that part is ok because I can grab the information as it's entered) but I have been struggling for about a week now to try to get the names of people who are signing out.
Basically a name and date/time get written to myExternalFile on btnAddTodo
is it possible in the btnDeleteDoneTodo to grab the names from deleteDoneTodos() and add them to myEternalFile??
class MainActivity : AppCompatActivity() {
private lateinit var todoAdapter: TodoAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
todoAdapter = TodoAdapter(mutableListOf())
rvTOdoItems.adapter = todoAdapter
rvTOdoItems.layoutManager = LinearLayoutManager(this)
val fileName = "log.txt"
val filePath = "Sign In\\"
val myExternalFile = File (getExternalFilesDir(filePath),fileName)
btnAddTodo.setOnClickListener {
val todoTitle = etTodoTitle.text.toString().uppercase()
val regTitle = etRegTitle.text.toString().uppercase()
if (todoTitle.isNotEmpty() && regTitle.isNotEmpty()) {
val todo = Todo(todoTitle+"\n"+regTitle)
myExternalFile.appendText(todoTitle +" "+ regTitle +" "+ todoAdapter.getCurrentDateTime() +"\n")
todoAdapter.addTodo(todo)
etTodoTitle.text.clear()
etRegTitle.text.clear()
}
}
btnDeleteDoneTodo.setOnClickListener {
todoAdapter.deleteDoneTodos()
}
}
class Todo (
val title : String,
var isChecked: Boolean = false
class TodoAdapter(
val todos: MutableList<Todo>
) : RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {
class TodoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
return TodoViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.todo,
parent,
false
)
)
}
fun getCurrentDateTime() : Date {
return Calendar.getInstance().time
}
fun addTodo (todo : Todo) {
todos.add(todo)
notifyItemInserted(todos.size-1)
}
fun deleteDoneTodos () {
todos.removeAll(Todo::isChecked)
notifyDataSetChanged()
}
private fun toggleStrikeThrough(tvtodoTitle: TextView, isChecked: Boolean) {
if (isChecked) {
tvtodoTitle.paintFlags = tvtodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG
} else {
tvtodoTitle.paintFlags = tvtodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
}
}
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
val curTodo = todos[position]
holder.itemView.apply {
tvTodoTitle.text = curTodo.title
cdDone.isChecked = curTodo.isChecked
toggleStrikeThrough(tvTodoTitle, curTodo.isChecked)
cdDone.setOnCheckedChangeListener { _, isChecked ->
toggleStrikeThrough(tvTodoTitle, isChecked)
curTodo.isChecked = !curTodo.isChecked
}
}
}
override fun getItemCount(): Int {
return todos.size
}
}
I have tried appending just about every variable I think would be relevant with .toString() in the btnDeleteDoneTodos method, I have tried making separate lists in the deleteDoneTodos function, I tried making a new function that outputs a list but I have failed.
I don't know if it's even possible with the way it has all been implemented - like I said I'm very new to programming so any help would be appreciated.
Thanks
edit.
I have managed to grab a single person leaving by adding the following leavers function to the todoAdapter class:
fun leavers (tvtodoTitle: TextView, isChecked: Boolean) : String {
var going = ""
if (isChecked) going = tvtodoTitle.text.toString()
return going
}
which returns a single string (hence only one person) that I can then append to my external file. I have tried to change the function to mutable list then iterate through it in btnDeleteDoneTodo listener but it will still only return one name.
I think I'm going to can this and go with Dewerros suggestion and try to setup a local Db, if anybody does have a solution it would still be gladly received as this has annoyed me for far too long now.
I have 20 fragments. I want to navigate fragments randomly with a button.It works correctly but I don't want to revisit the same fragments. If I navigate from fragment 5 to 9, I don't want to re-navigate to 5 anymore. I want to see every fragment just once. In every fragment I did this:
btnNavigateNewFragment.setOnClickListener {
val list: MutableList<Int> = mutableListOf(
R.id.nav_new_game_eighteen,
R.id.nav_dealgame,
R.id.nav_new_game_one,
R.id.nav_new_game_two,
R.id.nav_new_game_three,
R.id.nav_new_game_four,
R.id.nav_new_game_five,
R.id.nav_new_game_six,
R.id.nav_new_game_seven,
R.id.nav_new_game_eight,
R.id.nav_new_game_nine,
R.id.nav_new_game_ten,
R.id.nav_new_game_eleven,
R.id.nav_new_game_twelve,
R.id.nav_new_game_thirteen,
R.id.nav_new_game_fourteen,
R.id.nav_new_game_fifteen,
R.id.nav_new_game_sixteen,
R.id.nav_new_game_seventeen
)
val randomFragment= list.random()
findNavController().navigate(randomFragment)
What logic should I use to not visit the fragment that navigated once ?
I would keep track of the visited fragments in an Activity-scoped ViewModel. If you keep it in a Fragment or Activity property, it will be lost if there's a configuration change.
class MyViewModel: ViewModel() {
private val destinations = listOf(
R.id.nav_new_game_eighteen,
R.id.nav_dealgame,
R.id.nav_new_game_one,
//...
R.id.nav_new_game_seventeen
)
private val remainingDestinations = mutableListOf<Int>().apply { addAll(destinations) }
fun getNextNavDestination(): Int {
if (remainingDestinations.isEmpty()) {
remainingDestinations.addAll(destinations)
// or whatever logic you want to do when all destinations have been used
}
val destination = remainingDestinations.random()
remainingDestinations.remove(destination)
return destination
}
}
In your fragment that goes to one of the random destinations:
class MyFragment: Fragment() {
private val myViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle) {
super.onViewCreated(view, savedInstanceState)
//...
btnNavigateNewFragment.setOnClickListener {
indNavController().navigate(myViewModel.getNextNavDestination())
}
}
}
class DialFragment: DialogFragment() {
private lateinit var image001: ImageView
private val caller = object: FingerprintManager.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
super.onAuthenticationError(errorCode, errString)
listener.onDialClick(errString.toString(), "2")
dismiss()
}
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult?) {
super.onAuthenticationSucceeded(result)
listener.onDialClick("yes","1")
var avp = image001.drawable as AnimatedVectorDrawable
avp.start()
dismiss()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
listener.onDialClick("no","3")
dismiss()
}
The animation called by avp.start() is not beeing shown, the dialog fragment directly dismisses after succesfull authentification. if i omit the dismiss()-Call below then it works properly. My question is why is this and how to fix it? And yes, i know FingerprintManager-API is deprecated, i am running this for test purposes.
dismiss() immediately ends the life of your DialogFragment, so it will have no more opportunity to show you any animations in the Fragment. You should dismiss after a delay (however long the animation is).
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult?) {
super.onAuthenticationSucceeded(result)
listener.onDialClick("yes","1")
var avp = image001.drawable as AnimatedVectorDrawable
avp.start()
activity?.runOnUiThread(300L) { // put however long your animation takes
dismiss()
}
}
I want to make my recyclerview paginationscrolling using retrofit.
I already complete get Json data using retrofit. It means interface API is correct from API Document.
However, if I loaded more 20items. can not scroll more items in Client.
when I checked Server data. per one page can get maximum 20items.
For example, if I loaded 25items in my recyclerview.
page 0: 20, page 1: 5.
if I want scrolling all items, How can I make paginationscrolling for retrofit??
check some my code and help me..
Response
Interface API
#GET("/store/cart/mine")
fun getCart(#Header("Authorization") token: String?, #Query("page") page:Int): Call<CartResponse>
CartViewActivity
class CartViewActivity : AppCompatActivity(), SwipeRefreshLayout.OnRefreshListener {
private val lastVisibleItemPosition: Int
get()= LinearLayoutManager.HORIZONTAL
private lateinit var scrollListener: RecyclerView.OnScrollListener
lateinit var mAdapter: CartItemRecyclerAdapter
#RequiresApi(Build.VERSION_CODES.N)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cart_view)
val page = 0
val token = SharedPreference.getTokenInfo(this)
Client.retrofitService.getCart(token,page).enqueue(object :Callback<CartResponse> {
override fun onResponse(call: Call<CartResponse>, response: Response<CartResponse>) {
if (response?.isSuccessful == true) {
swipeRefreshLo.setOnRefreshListener(this#CartViewActivity)
showdata(response.body()?.docs!!)
}else if(response?.isSuccessful==false) {
val er = Gson().fromJson(response.errorBody()?.charStream(), ErrorResponse::class.java)
if (er.code==60202) {
}
}
}
override fun onFailure(call: Call<CartResponse>, t: Throwable) {
}
})
}
private fun showdata(results: MutableList<cartDocs>) {
recycler_view.apply {
mAdapter=CartItemRecyclerAdapter(context,context as Activity, results)
recycler_view.adapter=mAdapter
recycler_view.layoutManager=LinearLayoutManager(context)
setRecyclerViewScrollListener()
}
}
private fun setRecyclerViewScrollListener() {
scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val totalItemCount = recyclerView.layoutManager!!.itemCount
if(totalItemCount == lastVisibleItemPosition + 1) {
Log.d("MyTAG", "Load new list")
recyclerView.removeOnScrollListener(scrollListener)
}
}
}
}
override fun onRefresh() {
swipeRefreshLo.isRefreshing = false
}
}
Firstly, you need to modify your adapter to be able to update existing data, instead of creating new adapter every time you fetch new data.
Adapter should be initialised before getting the data, to be able to call methods on it.
You should create a method inside the adapter, something like
fun updateData(results: MutableList<cartDocs>) {
dataSet.addAll(results)
notifyItemRangeInserted(start, newItemsSize)
}
Then, we you get response from server in onSuccess() you should call method above, and increment page, so the next time you load data, you get new items.
Data should be fetched first time when screen loads (page will be 0), and then when user scrolls to bottom of RV ().
When you press the button to open a separate input window, there is a function to display the results toast.
class MainActivity : AppCompatActivity() {
val disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
val f = TestPopup()
usingRxJava(f)
//usingLiveData(f)
}
}
private fun usingRxJava(f: TestPopup) {
val subject = SingleSubject.create<String>()
f.show(supportFragmentManager, "TAG")
button.post {
f.dialog.setOnDismissListener {
val str = f.arguments?.getString(TestPopup.TEST_KEY) ?: ""
subject.onSuccess(str)
}
}
subject.subscribe({
Toast.makeText(this, "Accept : $it", Toast.LENGTH_SHORT).show()
}, {
}).addTo(disposable)
}
private fun usingLiveData(f: TestPopup) {
val liveData = MutableLiveData<String>()
f.show(supportFragmentManager, "TAG")
button.post {
f.dialog.setOnDismissListener {
val str = f.arguments?.getString(TestPopup.TEST_KEY) ?: ""
liveData.postValue(str)
}
}
liveData.observe(this, Observer {
Toast.makeText(this, "Accept : $it", Toast.LENGTH_SHORT).show()
})
}
override fun onDestroy() {
disposable.dispose()
super.onDestroy()
}
}
DialogFragment
class TestPopup : DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.dialog_test, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
button_test.setOnClickListener {
val arg = Bundle()
arg.putString(TEST_KEY, edit_test.text.toString())
arguments = arg
dismiss()
}
}
companion object {
const val TEST_KEY = "KEY"
}
}
(Sample Project Url : https://github.com/heukhyeon/DialogObserverPattern )
This sample code works in normal cases. However, the toast does not float after the following procedure.
Developer Option - Dont'keep activities enable
Open TestPopup, and enter your text. (Do not press OK button)
Press the home button to move the app to the background
The app is killed by the system.
Reactivate the app (Like clicking on an app in the apps list)
In this case, the text I entered remains on the screen, but nothing happens when I press the OK button.
Of course I know this happens because at the end of the activity the observe relationship between the activity and the Dialog is over.
Most of the code uses the implementation of the callback interface for that Dialog in the Activity to handle that case.
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
button_test.setOnClickListener {
val input = edit_test.text.toString()
(activity as MyListener).inputComplete(input)
dismiss()
}
}
class MainActivity : AppCompatActivity(), TestPopup.MyListener {
override fun inputComplete(input: String) {
Toast.makeText(this, "Accept : $input", Toast.LENGTH_SHORT).show()
}
}
But I think it's a way that doesn't match the Observer pattern, and I want to implement it using the Observer pattern as much as possible.
I'm thinking of getting a Fragment from the FragmentManager and subscribing again at onCreate, but I think there's a better way.
Can someone help me?
Your understanding of the problem is correct, except that the problem happens with any configuration changes, including screen rotation. You can reproduce issue without using the developer mode. Try this for example:
Open TestPopup, and enter your text. (Do not press OK button)
Rotate screen
See toast message not popping up.
Also note that your "observer pattern" implementation is not a proper observer pattern. Observer pattern has a subject and an observer. In your implementation, the activity is acting as both the subject and the observer. The dialog is not taking any part in this observer pattern, and using .setOnDismissListener is just another form of a listener pattern.
In order to implement observer pattern between the Fragment(the subject) and the Activity(the observer), the Activity needs to get the reference of the Fragment using the FragmentManager as you suggested. I suggest to use view model and establish observer pattern between view layer and view model layer instead.
RxJava example:
//MainViewModel.kt
class MainViewModel: ViewModel() {
val dialogText = PublishProcessor.create<String>()
fun postNewDialogText(text: String) {
dialogText.onNext(text)
}
}
// Activity
val disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
viewModel.dialogText.subscribe {
Toast.makeText(this, "Accept : $it", Toast.LENGTH_SHORT).show()
}.addTo(disposable)
button.setOnClickListener {
TestPopup().show(supportFragmentManager, "TAG")
// usingRxJava(f)
// usingLiveData(f)
}
}
override fun onDestroy() {
disposable.dispose()
super.onDestroy()
}
// Dialog Fragment
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// Important!! use activity when getting the viewmodel.
val viewModel = ViewModelProviders.of(requireActivity()).get(MainViewModel::class.java)
button_test.setOnClickListener {
viewModel.postNewDialogText(edit_test.text.toString())
dismiss()
}
}