I'm trying to finish an activity from another (android) with kotlin. I know the wat to do it with java is with the following code (https://stackoverflow.com/a/10379275/7280257)
at the first activity:
BroadcastReceiver broadcast_reciever = new BroadcastReceiver() {
#Override
public void onReceive(Context arg0, Intent intent) {
String action = intent.getAction();
if (action.equals("finish_activity")) {
finish();
// DO WHATEVER YOU WANT.
}
}
};
registerReceiver(broadcast_reciever, new IntentFilter("finish_activity"));
On the other activity:
Intent intent = new Intent("finish_activity");
sendBroadcast(intent);
For some reason converting the java activity to kotlin doesn't give a valid output, if someone could give me the correct syntax to do it properly with kotlin I will appreciate it
kotlin output (first activity) [OK]:
val broadcast_reciever = object : BroadcastReceiver() {
override fun onReceive(arg0: Context, intent: Intent) {
val action = intent.action
if (action == "finish_activity") {
finish()
// DO WHATEVER YOU WANT.
}
}
}
registerReceiver(broadcast_reciever, IntentFilter("finish_activity"))
kotlin output (2nd activity) [OK]
val intent = Intent("finish_activity")
sendBroadcast(intent)
ERROR: http://i.imgur.com/qaQ2YHv.png
FIX: THE CODE SHOWN IS RIGHT, YOU JUST NEED TO PLACE IT INSIDE THE onCreate FUNCTION
Simple code to finish a particular activity from another:
class SplashActivity : AppCompatActivity(), NavigationListner {
class MyClass{
companion object{
var activity: Activity? = null
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MyClass.activity = this#SplashActivity
}
override fun navigateFromScreen() {
val intent = Intent(this,LoginActivity::class.java)
startActivity(intent)
}
}
Now call SplashActivity.MyClass.activity?.finish() from another activity to finish above activity.
The error Expecting member declaration is there because you wrote a statement (the function call) inside a class. In that scope, declarations (functions, inner classes) are expected.
You have to place your statements inside functions (and then call those from somewhere) in order for them to be executed.
Related
We have been discussing about this but we don't know the reason of creating a viewmodel factory to create a viewmodel instead of instantiate the viewmodel directly. What is the gain of creating a factory that just creates the viewmodel?
I just put a simple example of how I did it without Factory
here is the kodein module:
val heroesRepositoryModel = Kodein {
bind<HeroesRepository>() with singleton {
HeroesRepository()
}
bind<ApiDataSource>() with singleton {
DataModule.create()
}
bind<MainViewModel>() with provider {
MainViewModel()
}
}
The piece of the Activity where I instantiate the viewmodel without using the factory
class MainActivity : AppCompatActivity() {
private lateinit var heroesAdapter: HeroAdapter
private lateinit var viewModel: MainViewModel
private val heroesList = mutableListOf<Heroes.MapHero>()
private var page = 0
private var progressBarUpdated = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this)
.get(MainViewModel::class.java)
initAdapter()
initObserver()
findHeroes()
}
The ViewModel where I instantiate the usecase directly without having it in the constructor
class MainViewModel : ViewModel(), CoroutineScope {
private val heroesRepository: HeroesRepository = heroesRepositoryModel.instance()
val data = MutableLiveData<List<Heroes.MapHero>>()
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = uiContext + job
fun getHeroesFromRepository(page: Int) {
launch {
try {
val response = heroesRepository.getHeroes(page).await()
data.value = response.data.results.map { it.convertToMapHero() }
} catch (e: HttpException) {
data.value = null
} catch (e: Throwable) {
data.value = null
}
}
}
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
So here a example using factory
class ListFragment : Fragment(), KodeinAware, ContactsAdapter.OnContactListener {
override val kodein by closestKodein()
private lateinit var adapterContacts: ContactsAdapter
private val mainViewModelFactory: MainViewModelFactory by instance()
private val mainViewModel: MainViewModel by lazy {
activity?.run {
ViewModelProviders.of(this, mainViewModelFactory)
.get(MainViewModel::class.java)
} ?: throw Exception("Invalid Activity")
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_list, container, false)
}
The viewmodelfactory:
class MainViewModelFactory (private val getContacts: GetContacts) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(getContacts) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
And the viewmodel:
class MainViewModel(private val getContacts: GetContacts) : BaseViewModel() {
lateinit var gamesList: LiveData<PagedList<Contact>>
var contactsSelectedData: MutableLiveData<List<Contact>> = MutableLiveData()
var contactsSelected: ArrayList<Contact> = ArrayList()
private val pagedListConfig by lazy {
PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(PAGES_CONTACTS_SIZE)
.setPageSize(PAGES_CONTACTS_SIZE)
.setPrefetchDistance(PAGES_CONTACTS_SIZE*2)
.build()
}
Here is the complete first example:
https://github.com/ibanarriolaIT/Marvel/tree/mvvm
And the complete second example:
https://github.com/AdrianMeizoso/Payment-App
We can not create ViewModel on our own. We need ViewModelProviders utility provided by Android to create ViewModels.
But ViewModelProviders can only instantiate ViewModels with no arg constructor.
So if I have a ViewModel with multiple arguments, then I need to use a Factory that I can pass to ViewModelProviders to use when an instance of MyViewModel is required.
For example -
public class MyViewModel extends ViewModel {
private final MyRepo myrepo;
public MyViewModel(MyRepo myrepo) {
this.myrepo = myrepo;
}
}
To instantiate this ViewModel, I need to have a factory which ViewModelProviders can use to create its instance.
ViewModelProviders Utility can not create instance of a ViewModel with argument constructor because it does not know how and what objects to pass in the constructor.
In short,
if we need to pass some input data to the constructor of the viewModel , we need to create a factory class for viewModel.
Like example :-
class MyViewModelFactory constructor(private val repository: DataRepository): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(MyViewModel::class.java!!)) {
MyViewModel(this.repository) as T
} else {
throw IllegalArgumentException("ViewModel Not Found")
}
}
}
Reason
We cannot directly create the object of the ViewModel as it would not be aware of the lifecyclerOwner. So we use :-
ViewModelProviders.of(this, MyViewModelFactory(repository)).get(MyViewModel::class.java)
We have been discussing about this but we don't know the reason of creating a viewmodel factory to create a viewmodel instead of instantiate the viewmodel directly. What is the gain of creating a factory that just creates the viewmodel?
Because Android will only give you a new instance if it's not yet created for that specific given ViewModelStoreOwner.
Let's also not forget that ViewModels are kept alive across configuration changes, so if you rotate the phone, you're not supposed to create a new ViewModel.
If you are going back to a previous Activity and you re-open this Activity, then the previous ViewModel should receive onCleared() and the new Activity should have a new ViewModel.
Unless you're doing that yourself, you should probably just trust the ViewModelProviders.Factory to do its job.
(And you need the factory because you typically don't just have a no-arg constructor, your ViewModel has constructor arguments, and the ViewModelProvider must know how to fill out the constructor arguments when you're using a non-default constructor).
When we are simply using ViewModel, we cannot pass arguments to that ViewModel
class GameViewModel() : ViewModel() {
init {
Log.d(TAG, "GameViewModel created")
}
}
However, in some cases, we need to pass our own arguments to ViewModel. This can be done using ViewModelFactory.
class ScoreViewModel(finalScore: Int) : ViewModel() {
val score = finalScore
init {
Log.d(TAG, "Final score: $finalScore")
}
}
And to instantiate this ViewModel, we need a ViewModelProvider.Factory as simple ViewModel cannot instantiate it.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
When it comes to instantiating object of this ViewModel i.e with ViewModelProvider, we pass ViewModelFactory as an argument which contains information about our custom arguments which we want to pass. It goes like:
viewModelFactory = ScoreViewModelFactory(score)
viewModel = ViewModelProvider(this,viewModelFactory).get(ScoreViewModel::class.java)
That is why factory methods are there.
I'm having trouble binding my ViewHolder, and I've got two warning that I believe are related. I am trying to use Hilt to create a clickable ViewHolder, so in my SessionAdapter I am using an inner class to bind my SessionViewHolder to my RecyclerView.
First, I am struggling to understand what to return for the inner class SessionViewHolder fun bind(session: Session) { ...}. Android Studio is telling me function "bind" is never used, but I thought I used it in my onBindViewHolder?
Secondly, in my override onBindViewHolder I don't understand how I should use val session?
#AndroidEntryPoint
class SessionFragment : Fragment() {
var adapter: SessionAdapter = SessionAdapter()
private val sessionAdapter = SessionListAdapter(this::onSessionClicked)
private fun onSessionClicked(session: Session): Session {
return(session)
}
class SessionAdapter {
fun setOnClickListener() {
return(addSessionToItinerary())
}
private fun addSessionToItinerary() {
return addSessionToItinerary()
}
}
class SessionListAdapter(
private val onSessionCLicked: (Session) -> Unit,
) : ListAdapter<Session, SessionListAdapter.SessionViewHolder>(SessionItemCallback) {
inner class SessionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind() {
val textView = itemView.findViewById<TextView>(0)
fun bind(session: Session) {
textView.text = session.title
itemView.setOnClickListener {
onSessionCLicked(session)
return#setOnClickListener
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SessionViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val itemView = layoutInflater.inflate(R.layout.fragment_session_list, parent, false)
return SessionViewHolder(itemView)
}
override fun onBindViewHolder(holder: SessionViewHolder, position: Int) {
val session = getItem(position)
holder.bind()
}
}
Thank you in advance for you help. I have gotten myself confused with the recurrence of bind and session throughout my adapter.
See here in this code, you have defined two bind functions, but one is nested inside the other so it is unusable:
fun bind() {
val textView = itemView.findViewById<TextView>(0)
fun bind(session: Session) {
textView.text = session.title
itemView.setOnClickListener {
onSessionCLicked(session)
return#setOnClickListener
}
}
}
The outer bind() function is the one you are calling, and it doesn't make sense to bind nothing. This function gets a reference to a TextView, and it creates a function that is never used.
Another problem is that you are passing 0 to findViewById. There is never going to be a view with an ID of 0. You need to pass R.id.whateverYourTextViewIsNamedInYourXml.
Side note, return#onClickListener is unnecessary. If a function doesn't return anything, putting a return statement on the last line doesn't do anything.
To make it work, you should replace the above code with something like this, but replace the name of the text view with whatever ID you assigned it in your XML:
fun bind(session: Session) {
val textView = itemView.findViewById<TextView>(R.id.myTextView)
textView.text = session.title
itemView.setOnClickListener {
onSessionCLicked(session)
}
}
and then pass the session to this function when you call it.
Side note, your SessionAdapter class doesn't make any sense at all, but you're not using it for anything anyway. I would delete that.
I'm new in kotlin and I'm trying to use retrofit with Rxjava and live data in MVVM architecture.
I config retrofit, and also use observable and subscribe in ViewModel to make observable variable to use in activity binding layout.
I have a button in my view and when I click on it, the request method gets to start and subscription write a log of its own data. but my variable gets null at first and after seconds, when retrofit completed its task, my variable gets data but log value doesn't update.
this is my retrofit initialize class
class ApiService {
private val INSTANCE =
Retrofit.Builder()
.baseUrl("http://www.janbarar.ir/App/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(IRetrofitMethods::class.java)
private fun <T> callBack(iDataTransfer: IDataTransfer<T>) =
object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val data = response.body()
if (data != null)
iDataTransfer.onSuccess(data)
else
try {
throw Exception("data is empty")
} catch (ex: Exception) {
iDataTransfer.onError(ex)
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
iDataTransfer.onError(t)
}
}
fun getCategories(iDataTransfer: IDataTransfer<List<Category>>) =
INSTANCE.getCategories().enqueue(callBack(iDataTransfer))
this is an interface for retrofit
#GET("GetCategories")
fun getCategories(): Call<List<Category>>
this is my model class. I think the problem is here. because the observable send null data before retrofit finish its work.
fun getCategories(): Observable<ArrayList<Category>> {
val result = arrayListOf<Category>()
api.getCategories(object : IDataTransfer<List<Category>> {
override fun onSuccess(data: List<Category>) {
result.addAll(data)
}
override fun onError(throwable: Throwable) {
Log.e("Model", throwable.message!!)
}
})
return Observable.just(result)
}
and this is also my ViewModel class
class ProductViewModel(private val model: ProductModel) : ViewModel() {
var isLoading = ObservableField(false)
var categoryList = MutableLiveData<ArrayList<Category>>()
private var compositeDisposable = CompositeDisposable()
fun getCategories() {
isLoading.set(true)
compositeDisposable +=
model.getCategories()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
categoryList.value = it
}, {
Log.e("ViewModel", it.message.toString())
})
isLoading.set(false)
}
finally, it's my activity
lateinit var binding: ActivityMainBinding
private val vm: ProductViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.vm = vm
vm.categoryList.observe(this, Observer {
if (it != null)
Log.e("activity", it.toString())
})
}
As ExpensiveBelly mentioned in a comment, Retrofit provides a call adapter for RxJava, so you can let your API return Observable<List<Category>> directly. To do this, you will need to add the RxJava call adapter dependency to your app module's build.gradle:
implementation 'com.squareup.retrofit2:adapter-rxjava2:(version)'
Add the call adapter factory when constructing your Retrofit instance:
private val INSTANCE =
Retrofit.Builder()
.baseUrl("http://www.janbarar.ir/App/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // add this
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(IRetrofitMethods::class.java)
And then just let your service return an Observable directly:
#GET("GetCategories")
fun getCategories(): Observable<List<Category>>
If ApiService needs to do some handling before the rest of the app gets the response, you can use RxJava operators like map.
It would be illustrative to see why your code doesn't work and how to fix it. When you call api.getCategories(someCallback), one of your callback methods will be called at some point in the future. In the meantime, the model.getCategories() method will return immediately.
When you subscribe to the returned Observable, it emits the result variable, which is currently an empty list. result will eventually have some data in it, but your code will not be informed of this at all.
What you really want to do is emit the list of categories when it becomes available. The standard way to get an Observable from a callback API is with Observable.create:
fun getCategories(): Observable<ArrayList<Category>> {
return Observable.create { emitter ->
api.getCategories(object : IDataTransfer<List<Category>> {
override fun onSuccess(data: List<Category>) {
emitter.onNext(data)
emitter.onComplete()
}
override fun onError(throwable: Throwable) {
emitter.onError(throwable)
}
})
}
}
Of course, it's better to just use RxJava2CallAdapterFactory if possible, since this work has already been done there.
I want to get input from the user using EditText and pass it to server and show the response to the user. I do this simply without any architecture but I would like to implement it in MVVM.
this is my repository code:
class Repository {
fun getData(context: Context, word: String): LiveData<String> {
val result = MutableLiveData<String>()
val request = object : StringRequest(
Method.POST,
"https://jsonplaceholder.typicode.com/posts",
Response.Listener {
result.value = it.toString()
},
Response.ErrorListener {
result.value = it.toString()
})
{
#Throws(AuthFailureError::class)
override fun getParams(): MutableMap<String, String> {
val params = HashMap<String, String>()
params["word"] = word
return params
}
}
val queue = Volley.newRequestQueue(context)
queue.add(request)
return result
}
}
and these are my View Model codes:
class ViewModel(application: Application) : AndroidViewModel(application) {
fun getData(word: String): LiveData<String> {
val repository = Repository()
return repository.getData(getApplication(), word)
}
}
and my mainActivity would be like this:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val model = ViewModelProviders.of(this).get(ViewModel::class.java)
model.getData("test").observe(this, Observer {
Log.i("Log", "activity $it")
})
}
}
My layout has an EditText which I want to get user input and pass it to the server, how should i do that?
Here how i did it in my projet.
You can probably use android annotations.
It's gonna requiere you to put some dependencies and maybe change the class a bit, but then you gonna link your Viewmodel with your repository and then you gonna have to program the setter of the variable to do a notifyChange() by making the class herited from BaseObservable. Then in the xml, if you did the correctly, you should be able to do a thing like text:"#={model.variable}" and it should be updating at the same time.
A bit hard and explain or to show for me sorry, but i would look into Android Annotations with #DataBinding, #DataBound :BaseObservable
https://github.com/androidannotations/androidannotations/wiki/Data-binding-support
Hope that can help a bit!
class FirstActivity : AppCompatActivity() {
companion object{
val USER_KEY="FirstActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_first)
button_firstActivity.setOnClickListener {
val string:String=textView_first.text.toString()
val intent=Intent(this,MainActivity::class.java)
intent.putExtra(USER_KEY,string)
startActivity(intent)
}
}
}
class MainActivity : AppCompatActivity() {
companion object{
val MAINUSERKEY="MainActivity"
var str:String=""
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
str=intent.getStringExtra(FirstActivity.USER_KEY)
textview_main.text=str
button_Run.setOnClickListener {
val edittextstring=editText1.text.toString()
val intent=Intent(this,MainActivity::class.java)
intent.putExtra(MAINUSERKEY,edittextstring)
startActivity(intent)
}
}
}
Hello every one! I am new to Android programming with Kotlin.
I have two activities, suppose A and B. I want to start activity B from A and when B starts, it will display the TextView string of A into TextView_Main.
It is working fine now. I want to start activity B again on clicking button_Run which is on Activity B and passing a string again which I entered in edittext of Activity B. And now it should be displayed on textview of Activity B, when it opens again.
Please help me do this.
The problem is that the edittext string is being stored as an intent extra with name MAINUSERKEY="MainActivityā€¯, which is different from the extra you are currently extracting on your MainActivity, the one with name USER_KEY="FirstActivityā€¯. So I would do something like this to ensure I get the correct string extra:
str = with(intent) {
getStringExtra(FirstActivity.USER_KEY) ?: getStringExtra(MainActivity.MAINUSERKEY) ?: "No string extra was found"
}
it is more clear to startActivity like code below, add this code in ActivityB
companion object{
private const val EXTRA_ MAIN_USERKEY = "EXTRA.MAIN_USERKEY"
fun getIntent(context:Context, userKey:String): Intent
{
val intent = Intent(context,ActivityB::class.java)
intent.putExtra(EXTRA_ MAIN_USERKEY, userKey)
return intent
}
}
and this code in ActivityA:
startActivity(ActivityB.getIntent(this,"some key"))
so every time you start activityB you should pass the string