I have two interfaces ...
interface Calculator { fun getResult(input: String?): Result? }
interface Result { val asString: String }
i am able to initialize Calculator interface, but when i am trying to Callback, i get an error message ...for looping Stackoverflow error message..
Any idea why? how can i solve this problem?
I know the problem is in this line
***override fun getResult(input:String?):Result=getResult(textInputEditText.text.toString()) ***-
but i dont know how to fix it, any help is really welcome! thanks in advance
for your time and help!!
class ComputeFragment #ContentView constructor() : Fragment(R.layout.fragment_task) {
private var taskBinding: FragmentTaskBinding? = null
private val calculator: Calculator? = null
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is Calculator) {
calculator = context
} else {
throw RuntimeException(context.toString() + " must implement
Calculator interface")
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState:Bundle?): View {
val binding = FragmentTaskBinding.inflate(layoutInflater)
taskBinding = binding
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
taskBinding
?.buttonFirst
?.setOnClickListener {
NavHostFragment.findNavController(this#ComputeFragment).navigate(R.id.action_FirstFragment_to_SecondFragment)
}
taskBinding
?.buttonCompute
?.setOnClickListener { v: View ->
if (calculator == null) {
Snackbar.make(v, R.string.please_implement, Snackbar.LENGTH_LONG).show()
return#setOnClickListener
}else {
val result = calculator.getResult(taskBinding?.textInputEditText?.text.toString())
taskBinding?.result?.text = result?.asString
}}}}
class TaskActivity : appCompatActivity() , Calculator {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_task)
setSupportActionBar(toolbar}
}
override fun getResult(input: String?): Result? =
getResult(textInputEditText.text.toString())
}}```
Related
This is my interface
interface R2OnClickFullListener {
fun onFullScreen(fullscreen: Boolean)
}
This is my fragment
class Rove2LiveVideoFragment : BaseFragment()
{
private lateinit var onFullScreenListener: R2OnClickFullListener
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_new_livevideo2, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
showBigView()
}
override fun onAttach(context: Context) {
inject(this)
super.onAttach(context)
if (activity is OnFullScreenListener) {
onFullScreenListener = activity as R2OnClickFullListener
} else {
throw ClassCastException(
activity.toString()
+ " must implement MyListFragment.OnItemSelectedListener"
)
}
}
private fun showBigView(){
onFullScreenListener.onFullScreen(true)
}
}
This is my activity
class Rove2MainActivity : BaseActivity(),R2OnClickFullListener{
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onFullScreen(fullscreen: Boolean) {
}
}
But i am getting class cast exception basically i am trying to pass data from fragment to activity so on attach fragment i am registering callback please help what i am doing wrong why the class cast exception is coming
below is error
Process: com.rovedashcam.android, PID: 31327
java.lang.ClassCastException:
com.rovedashcam.newmodeule.rove2dashcam.main.home.view.Rove2MainActivity#82ae1e0
must implement MyListFragment.OnItemSelectedListener
at
com.rovedashcam.newmodeule.rove2dashcam.main.livevideo.view.Rove2LiveVideoFragment.onAttach(Rove2LiveVideoFragment.kt:80)
it should be
if (activity is Rove2MainActivity ) {
onFullScreenListener = activity as R2OnClickFullListener
} else {
throw ClassCastException(
activity.toString()
+ " must implement MyListFragment.OnItemSelectedListener"
)
}
I recently started studying android kotlin and I am expecting this to have a very simple answer but I have a ViewModel which has a method called getDataComment and I want to call the method from my fragment and provide the needed argument for it. But when I try to call it, there's no build error, it just doesn't show the list.
My method in view model:
fun getDataComment(postId: Int) {
PostRepository().getDataComment(postId).enqueue(object : Callback<List<Comment>>{
override fun onResponse(call: Call<List<Comment>>, response: Response<List<Comment>>) {
val comments = response.body()
comments?.let {
mPostsComment.value = comments!!
}
}
override fun onFailure(call: Call<List<Comment>>, t: Throwable) {
Log.e(TAG, "On Failure: ${t.message}")
t.printStackTrace()
}
})
}
Fragment Class
class PostDetailFragment : Fragment() {
var param2: Int = 0
private lateinit var binding: FragmentPostDetailBinding
private val viewModel by viewModels<PostCommentViewModel>()
private val gson: Gson by lazy {
Gson()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding =
DataBindingUtil.inflate(inflater, R.layout.fragment_post_detail, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
checkArguments()
}
private fun checkArguments() {
arguments?.let { bundle ->
if (bundle.containsKey("POST")) {
val postString = bundle.getString("POST", "")
val post: Post = gson.fromJson(postString, Post::class.java)
binding.postDetailstvTitle.text = post.title
binding.postDetailstvBody.text = post.body
param2 = post.id
viewModel.getDataComment(param2)
}
}
}
}
Here's what it's supposed to look like: 1
Here's what I'm getting: 2
Sorry in advance if It's messy this is my first time asking here and let me know if you need more information. Thank you!
observe mPostsComment inside fragment from viewmodel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
checkArguments()
viewModel.mPostsComment.observe(viewLifecycleOwner, { collectionList ->
// do whatever you want with collectionList
})
}
I'm trying to inflate a custom dialog in my "CreateShoppingListMenuFragment" I've followed android's documentation but seem to be having a problem with the Listener, I know FragmentManager() is deprecated and used both parentFragmentManager & child FragmentManager to no success, maybe it's related?
Here is the error message:
logo1200.shoppinglist, PID: 24194
java.lang.ClassCastException: com.camilogo1200.shoppinglist.presentation.MainActivity#11852bbmust implement ShoppingListNameRequestListener
at com.camilogo1200.shoppinglist.presentation.fragments.ShoppingListNameRequestDialog.onAttach(ShoppinListNameRequestDialog.kt:68)
at androidx.fragment.app.Fragment.performAttach(Fragment.java:2922)
at androidx.fragment.app.FragmentStateManager.attach(FragmentStateManager.java:464)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:275)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
This is my DialogFragment:
class ShoppingListNameRequestDialog : DialogFragment() {
private lateinit var listener: ShoppingListNameRequestListener
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
val inflater: LayoutInflater = requireActivity().layoutInflater
val requestNameView = inflater.inflate(R.layout.shopping_list_name_request_dialog, null)
val nameInput = requestNameView.findViewById<TextView>(R.id.shopping_list_dialog_input)
var listName = ""
builder.setView(requestNameView)
.setPositiveButton(R.string.save_shopping_list,
DialogInterface.OnClickListener {dialog, id ->
if(nameInput.text.toString() != "")
listName = nameInput.text.toString()
listener.onDialogPositiveClick(this,listName);
})
.setNegativeButton(R.string.cancel,
DialogInterface.OnClickListener{dialog, id ->
listener.onDialogNegativeClick(this)
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
interface ShoppingListNameRequestListener {
fun onDialogPositiveClick(dialog: DialogFragment,listName:String)
fun onDialogNegativeClick(dialog: DialogFragment)
}
override fun onAttach(context: Context) {
super.onAttach(context)
try {
listener = context as ShoppingListNameRequestListener
} catch (e: ClassCastException) {
throw ClassCastException((context.toString() +
"must implement ShoppingListNameRequestListener"))
}
}
This is my "CreateShoppingListMenuFragment" (the host fragment where I'm inflating the dialog):
class CreateShoppingListMenuFragment : Fragment(),
ShoppingListNameRequestDialog.ShoppingListNameRequestListener {
private lateinit var binding: FragmentCreateShoppingListMenuBinding
private val viewModel: CreateShoppingListMenuViewModel by activityViewModels()
private val args: CreateShoppingListMenuFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(
layoutInflater,
R.layout.fragment_create_shopping_list_menu,
container,
false
)
binding.lifecycleOwner = this
viewModel.createItems()
viewModel.viewState.observe(viewLifecycleOwner, ::handleViewState)
val login = args.ownerName
val listId = args.listId
viewModel.setOwnerAndList(login, listId)
binding.createItemButton.setOnClickListener {
val directionToFragment =
CreateShoppingListMenuFragmentDirections.actionCreateShoppingListMenuFragmentToCreateItemMenuFragment(
login,
listId
)
Navigation.findNavController(binding.root).navigate(directionToFragment)
}
binding.completeShoppingListButton.setOnClickListener {
showNoticeDialog()
}
return binding.root
}
private fun showNoticeDialog() {
val dialog = ShoppingListNameRequestDialog()
dialog.show(parentFragmentManager, "ShoppingListNameRequestDialog")
}
override fun onDialogPositiveClick(dialog: DialogFragment,listName: String) {
val result = viewModel.saveShoppingList(listName)
Log.i("shoppingListResult", "$result")
// travel to final fragment sent shoppinglist as arg
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
// User touched the dialog's negative button
}
private fun handleViewState(viewState: CreateShoppingListMenuViewState) {
when (viewState) {
is CreateShoppingListMenuViewState.ErrorViewState -> showError(viewState.exception as ShoppingException)
//is RegisterViewState.SuccessViewState ->showSuccess()
else -> showSuccess(viewState)
}
}
private fun showSuccess(viewState: CreateShoppingListMenuViewState) {
val receivedList = viewState as CreateShoppingListMenuViewState.SuccessViewState
val dataList = receivedList.data
val adapter = ShoppingListMenuAdapter(dataList, viewModel::changeItemCount)
binding.itemListArray.adapter = adapter
}
private fun showError(exception: ShoppingException) {
if (exception.idError as? ItemError == ItemError.NO_ITEMS_CREATED) {
val message = getString(R.string.no_items_created_error_messages)
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
}
Any help would be greatly appreciated!
The Context in onAttach(Context context) is context Activity fragment does not have its own Context .
The problem here is you are casting context to ShoppingListNameRequestListener for this to work your Activity needs to implement the listener .
To solve this problem there are several ways. if we go with your approach we can pass fragment instance as targetFragment and use it as listener inside the DialogFragment .
private fun showNoticeDialog() {
val dialog = ShoppingListNameRequestDialog()
dialog.setTargetFragment(this)
dialog.show(parentFragmentManager, "ShoppingListNameRequestDialog")
}
Then inside dialog you can do something like this .
class ShoppingListNameRequestDialog:DialogFragment(){
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
targetFragment?.let {
listener = it as ShoppingListNameRequestListener
}
}
}
However setTargetFragment is deprecated now. As a Alternate way you can do the same with a shared ViewModel or with the new API FragmentResultListener.
The situation is pretty straightforward. I have a simple android app with 4 fragments displayed through a bottom navigation bar, and a central Room database. Each fragment should be able to perform CRUD operations on the DB through a viewmodel (details are probably irrelevant but I'll show this as well to be sure):
class ViewModel(application: Application): AndroidViewModel(application) {
val readAllIngredients: LiveData<List<Ingredient>>
val readAllRecipes: LiveData<List<Recipe>>
private val ingredientRepository: IngredientRepository
private val recipeRepository: RecipeRepository
init {
val ingredientDAO = ShoppingAppDatabase.getDatabase(application).ingredientDAO()
val recipeDAO = ShoppingAppDatabase.getDatabase(application).recipeDAO()
ingredientRepository = IngredientRepository(ingredientDAO)
recipeRepository = RecipeRepository(recipeDAO)
readAllIngredients = ingredientRepository.allIngredients
readAllRecipes = recipeRepository.allRecipes
}
fun addIngredient(ingredient: Ingredient) {
viewModelScope.launch(Dispatchers.IO) {
ingredientRepository.put(ingredient)
}
}
fun deleteIngredient(ingredient: Ingredient) {
viewModelScope.launch(Dispatchers.IO) {
ingredientRepository.delete(ingredient)
}
}
fun addRecipe(recipe: Recipe) {
viewModelScope.launch(Dispatchers.IO) {
recipeRepository.put(recipe)
}
}
fun updateRecipe(recipe: Recipe) {
viewModelScope.launch(Dispatchers.IO) {
recipeRepository.update(recipe)
}
}
fun updateIngredient(ingredient: Ingredient) {
viewModelScope.launch(Dispatchers.IO) {
ingredientRepository.update(ingredient)
}
}
}
I'm initializing a viewmodel in each fragment, but with limited success. Here's a fragment for which everything works fine:
class InventoryFragment() : Fragment() {
private var listAdapter = IngredientAdapter()
private lateinit var viewModel : ViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_inventory, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listAdapter = IngredientAdapter()
recycler_view.apply {
layoutManager = LinearLayoutManager(activity)
adapter = listAdapter
}
viewModel = ViewModelProvider(this).get(ViewModel::class.java)
viewModel.readAllIngredients.observe(viewLifecycleOwner, Observer { ingredient -> listAdapter.setData(ingredient) })
add_button.setOnClickListener{
val errorMessages = validateInput()
if(errorMessages.isNotEmpty()) {
displayToast(activity, errorMessages)
}
else {
viewModel.addIngredient(Ingredient(
edit_name.text.toString(),
edit_qty.text.toString().toFloat(),
edit_um.text.toString()
))
listAdapter.notifyDataSetChanged()
displayToast(activity, "Ingredient added")
hideKeyboard(activity, requireView().windowToken)
}
clearInput()
}
}
}
I'm initializing it in the onViewCreated callback and yeah, it works fine. Doing the same thing in a different fragment yields.. different results for some reason.
class BrowseFragment() : Fragment() {
private lateinit var viewModel: ViewModel
var recipeAdapter = RecipeAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_browse, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(ViewModel::class.java)
viewModel.readAllRecipes.observe(viewLifecycleOwner, Observer { recipe -> recipeAdapter.setData(recipe) })
submit_button.setOnClickListener{
var submitFragment = SubmitFragment(recipeAdapter)
var tr = (view.context as FragmentActivity).supportFragmentManager.beginTransaction()
tr.replace(R.id.fragment_container, submitFragment)
tr.commit()
}
browse_recycler_view.apply {
layoutManager = LinearLayoutManager(activity)
adapter = recipeAdapter
}
}
}
When I try to initialize the viewmodel in onViewCreated, I get an IllegalStateException: Can't access ViewModels from detached fragment exception. Creating it in onCreate doesn't work either, since the lifecycle owner is null, which makes sense I guess. What exactly am I doing wrong here?
I just started learning MVVM and I am unable to find out why I am getting response from retrofit multiple times.
I am following the videos on how to implement MVVM in kotlin and I would be very glad if you suggested better approach to this.
This is the Repository (I am using jsonplaceholder to get users):
class HelpersRepository(val application: Application) {
val showProgress = MutableLiveData<Boolean>()
val helpersList = MutableLiveData<List<Helpers>>()
fun getAllHelpers() {
showProgress.value = true
val destinationService = ServiceBuilder.buildService(HelpersNetwork::class.java)
val requestCall = destinationService.getHelpers()
requestCall.enqueue(object : Callback<List<Helpers>>{
override fun onResponse(call: Call<List<Helpers>>, response: Response<List<Helpers>>) {
helpersList.value = response.body()
Log.d("response", "${helpersList.value}")
showProgress.value = false
}
override fun onFailure(call: Call<List<Helpers>>, t: Throwable) {
showProgress.value = false
Toast.makeText(application, "Error while fetching the data", Toast.LENGTH_SHORT).show()
}
})
}}
This is the ViewModel:
class HelpersViewModel(application: Application) : AndroidViewModel(application) {
private val repository = HelpersRepository(application)
val showProgress : LiveData<Boolean>
val helpersList : LiveData<List<Helpers>>
init {
this.showProgress = repository.showProgress
this.helpersList = repository.helpersList
}
fun getAllHelpers() {
repository.getAllHelpers()
}
}
And this is my Fragment:
class HelpersFragment : Fragment() {
private lateinit var viewModel: HelpersViewModel
private lateinit var adapter: HelpersAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_helpers, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(HelpersViewModel::class.java)
adapter = HelpersAdapter(requireContext())
rv_helpers.adapter = adapter
viewModel.getAllHelpers()
viewModel.showProgress.observe(viewLifecycleOwner, Observer {
if (it) {
pb_helpers.visibility = View.VISIBLE
} else {
pb_helpers.visibility = View.GONE
}
})
viewModel.helpersList.observe(viewLifecycleOwner, Observer {
adapter.setHelpersList(it)
})
}
}
when viewModel.getAllHelpers() gets called in the fragment, the log displays the response three times (I am unable to copy the log as StackOverflow considers it as a spam).
P.S. If I put viewModel.getAllHelpers() inside a setOnclickListener on button, then it only fetches data one time.
I have searched everywhere but was unable to get the answer fitted to my question. I realize I am doing something wrong, but can't figure out exactly what
I finally figured out that my problem was coming from choosing dark mode in MainActivity. Turns out you must call:
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
before setContentView() in onCreate