I have a question. I have 2 activities in each I initialize the broadcast receiver to obtain internet connection changes. After changes are received, I notify the user about it by displaying some text on the screen.
In order not to write the code several times, I created BroadcastActivity that inherits from AppCompatActivity.
Now I pass views with super() to BroadcastActivity and manipulate with views (Change texts, visibilities etc.).
Question is: Is it best practice to do so or is there a more elegant solution? And if it can lead to memory leaks?
Thank you for your help!
Example of BroadcastActivity
open class BroadcastActivity : AppCompatActivity(), OnConnectionChangeListener {
//some code
}
And example of activity (Secondary activity structure similar to this)
public class MainActivity extends BroadcastActivity{
//some code
}
P.S. BroadcastActivity written on Kotlin and MainActivity on Java.
P.S. 2 At this moment I can't switch to single activity and looking for temporary solution.
To further elaborate on my comment, here's something "better" (again, in my personal opinion) ...
ActivityKtx.kt:
fun Activity.applyBroadcastReceiver(broadcastReceiver: BroadcastReceiver) {
lifecycle.addObserver(
BroadcastReceiverObserver(
BroadcastReceiverConfigurationImpl(broadcastReceiver, this)
)
)
}
BroadcastReceiverObserver.kt:
/**
* Class implementing [DefaultLifecycleObserver] which is in charge of delegating setting the
* [BroadcastReceiver]'s registration status.
* Registers in [Activity.onResume] and unregisters in [Activity.onPause]
*/
class BroadcastReceiverObserver(
val broadcastReceiverConfig: BroadcastReceiverConfiguration
) : DefaultLifecycleObserver {
var isBroadcastReceiverRegistered = false
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
if (isBroadcastReceiverRegistered.not()) {
broadcastReceiverConfig.registerBroadcastReceiver()
isBroadcastReceiverRegistered = true
}
}
override fun onStop(owner: LifecycleOwner) {
if (isBroadcastReceiverRegistered) {
isBroadcastReceiverRegistered = false
broadcastReceiverConfig.unregisterBroadcastReceiver()
}
super.onStop(owner)
}
// add same things for onStart() and onPause()
}
/**
* Base class for handling a [BroadcastReceiver] registration status
*/
abstract class BroadcastReceiverConfiguration {
abstract fun registerBroadcastReceiver()
abstract fun unregisterBroadcastReceiver()
}
/**
* Base class for handling the broadcast receiver
*
* #see [BroadcastReceiverObserver]
*/
class BroadcastReceiverConfigurationImpl constructor(
private val broadcastReceiver: BroadcastReceiver,
private val hostActivity: Activity
) : BroadcastReceiverConfiguration() {
override fun registerBroadcastReceiver() {
//ConnectivityManager.CONNECTIVITY_ACTION - deprecated
hostActivity.registerReceiver(broadcastReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
}
override fun unregisterBroadcastReceiver() {
hostActivity.unregisterReceiver(broadcastReceiver)
}
}
then just use it like this:
SomeActivity.kt:
class SomeActivity extends AppCompatActivity {
override fun onCreate() {
super.onCreate()
applyBroadcastReceiver(myBroadcastReceiver)
}
and voila. zero need for subclassing the Activity / AppCompatActivity / MyBaseActivity or whatever.
Related
I got a method() in the fragment. Fragment in host activity. So I need to call the method() from fragment ONLY when host activity was in onDestroy() . Maybe it suppose to be something like a static flag?
You can, by SupportFragmentManager, get a Fragment by layout id, tag or name.
But the most important thing here is in some situations Activity.onDestroy() method could never be called. So be careful if you implement code that needs to be executed, like an unsubscribe logic or removing callbacks.
class MyActivity: AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
val fragment = supportFragmentManager.findFragmentById(R.id.my_fragment) as MyFragment
if (fragment != null) {
fragment.myMethod()
}
}
}
class MyFragment: Fragment() {
fun myMethod() {
}
}
Or use a ViewModel to handle communication between the activity and the fragment.
https://developer.android.com/topic/libraries/architecture/viewmodel#sharing
I want to know what is the best approach to display some sort of message in the view from the ViewModel. My ViewModel is making a POST call and "onResult" I want to pop up a message to the user containing a certain message.
This is my ViewModel:
public class RegisterViewModel extends ViewModel implements Observable {
.
.
.
public void registerUser(PostUserRegDao postUserRegDao) {
repository.executeRegistration(postUserRegDao).enqueue(new Callback<RegistratedUserDTO>() {
#Override
public void onResponse(Call<RegistratedUserDTO> call, Response<RegistratedUserDTO> response) {
RegistratedUserDTO registratedUserDTO = response.body();
/// here I want to set the message and send it to the Activity
if (registratedUserDTO.getRegisterUserResultDTO().getError() != null) {
}
}
});
}
And my Activity:
public class RegisterActivity extends BaseActivity {
#Override
protected int layoutRes() {
return R.layout.activity_register;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
ActivityRegisterBinding binding = DataBindingUtil.setContentView(this, layoutRes());
binding.setViewModel(mRegisterViewModel);
}
What would the best approach be in this case?
We can use a SingleLiveEvent class as a solution. But it is a LiveData that will only send an update once. In my personal experience, using an Event Wrapper class with MutableLiveData is the best solution.
Here is a simple code sample.
Step 1 :
Create an Event class (this is a boilerplate code you can reuse for any android project).
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
Step 2 :
At the top of your View Model class, define a MutableLiveData with wrapper (I used a String here, but you can use your required data type), and a corresponding live data for encapsulation.
private val statusMessage = MutableLiveData<Event<String>>()
val message : LiveData<Event<String>>
get() = statusMessage
Step 3 :
You can update the status message within the functions of the ViewModel like this:
statusMessage.value = Event("User Updated Successfully")
Step 4 :
Write code to observe the live data from the View (activity or fragment)
yourViewModel.message.observe(this, Observer {
it.getContentIfNotHandled()?.let {
Toast.makeText(this, it, Toast.LENGTH_LONG).show()
}
})
Display Toast/snackbar message in view (Activity/Fragment) from viewmodel using LiveData.
Step:
Add LiveData into your viewmodel
View just observe LiveData and update view related task
For example:
In Viewmodel:
var status = MutableLiveData<Boolean?>()
//In your network successfull response
status.value = true
In your Activity or fragment:
yourViewModelObject.status.observe(this, Observer { status ->
status?.let {
//Reset status value at first to prevent multitriggering
//and to be available to trigger action again
yourViewModelObject.status.value = null
//Display Toast or snackbar
}
})
I know probably there is no clear answer for this question.
But I would like to know Your opinions and maybe new ideas.
I'm wondering which of the following options is the best/right/correct way to build the app-level Dagger Component in Application class.
Example 1:
public class MyApp extends Application {
private NetComponent mNetComponent;
#Override
public void onCreate() {
super.onCreate();
mNetComponent = DaggerNetComponent.builder()
.appModule(new AppModule(this))
.netModule(new NetModule("https://api.github.com"))
.build();
}
public NetComponent getNetComponent() {
return mNetComponent;
}
}
Usage:
((MyApp) getApplication()).getNetComponent().inject(this);
Example 2:
class MyApplication extends Application {
private static MyComponent component;
#Override
void onCreate() {
component = DaggerMyComponent.builder()
.contextModule(new ContextModule(getApplicationContext()))
.build();
}
public static MyComponent getMyComponent() {
return component;
}
}
Usage:
MyApplication.getMyComponent().inject(this)
Example 3:
class CustomApplication: Application() {
lateinit var component: SingletonComponent
private set
override fun onCreate() {
super.onCreate()
INSTANCE = this
component = DaggerSingletonComponent.builder()
.contextModule(ContextModule(this))
.build()
}
companion object {
private var INSTANCE: CustomApplication? = null
#JvmStatic
fun get(): CustomApplication = INSTANCE!!
}
}
Then:
class Injector private constructor() {
companion object {
#JvmStatic
fun get() : SingletonComponent = CustomApplication.get().component
}
}
Usage:
Injector.get().catRepository()
Example 4:
class App : Application() {
var repositoryComponent: RepositoryComponent? = null
var appComponent: AppComponent? = null
override fun onCreate() {
super.onCreate()
instance = this
appComponent = DaggerAppComponent.builder().application(this).build()
repositoryComponent = DaggerRepositoryComponent.builder().build()
}
companion object {
private var instance: App? = null
fun get(): App {
return instance!!
}
}
}
Usage:
App.get().repositoryComponent!!.inject(this)
What do you think about this? Is there any better / cleaner way to do this? Maybe provided examples are fine? Or maybe just one of them?
I will be grateful for any good examples / tips / advices.
Thanks!
Okay, no one answered in 5 days so it's my turn, despite my bias :p
Option #1
((MyApp) getApplication()).getNetComponent().inject(this);
It's an "ok" version of doing things, except for two things.
First, the name. NetComponent isn't really for networking, it's the app-global singleton component, so it should be either called SingletonComponent or AppComponent. But naming it NetComponent is disingenuous, it's typically responsible for everything else too.
Second problem is that you need a reference to Context to access your dependency graph, making Context actually be a dependency rather than it being provided to you.
Option #2
MyApplication.getMyComponent().inject(this)
This is a perfectly fine way of doing things, but you need to know that to reach your object graph, you need to access the static method of MyApplication.
Option #3
Injector.get().inject(this)
Internally, this solution actually just calls over to get the app component, public static AppComponent get() { return MyApplication.getInstance().getComponent(); }
The benefit is that getComponent() is exposed via an instance method of Application, so it could be theoretically swapped out.
Also, invoking a method on something called Injector.get() is more obviously an "injector" than, well, an application class.
As for whether you use .catRepository() or .inject(this), it's up to you; but I personally prefer calling the provision methods to get the deps in Activity/Fragment, because listing the member-injection targets adds a lot of clutter to the component over time.
4.)
App.get().repositoryComponent!!.inject(this)
You can ditch the !! if repositoryComponent is a lateinit var.
Having two components for the same scope (and therefore two different object graphs) will only cause trouble, out of all of the options, this is the worst.
In my opinion, the 3rd option is the best. Technically it's the same as option #2 with an additional "indirection" through the instance method of Application that actually returns the component.
I'm using Kotlin in android studio to make an app.
In my main activity I have a function changeText() that changes the text of a textbox.
I have a class that I'm implementing called VerificationListener() that when created will do things then call onVerified(), however I cannot call changeText from onVerified, is there a way to do so? The example I'm working off of is in Java and does it.
Example I'm working off of
public void onVerified() {
mIsVerified = true;
Log.d(TAG, "Verified!");
hideProgressAndShowMessage(R.string.verified);
showCompleted();}
Above is within the class, below is just sitting in the activity
private void showCompleted() {
ImageView checkMark = (ImageView) findViewById(R.id.checkmarkImage);
checkMark.setVisibility(View.VISIBLE);
}
If by "I cannot call changeText from onVerified" you mean that you have a VerificationListener as a separate standalone class and from that class you cannot call methods on the Activity, you should either a) make the VerificationListener an inner class of the Activity, b) pass your activity into the VerificationListener when it's created (be aware of the lifecycle) c) implement some messaging solution (broadcast receiver, startActivity + onIntent(), observable, or even an event bus (not advisable). Here is a sample implementation for b:
class MyActivity : Activity(), VerificationListener.OnVerifiedCallback {
fun onVerified() {
changeText()
}
override fun onCreate(state: Bundle) {
super.onCreate(state)
VerificationListener(this).doStuff()
}
}
class VerificationListener(internal var callback: OnVerifiedCallback) {
interface OnVerifiedCallback {
fun onVerified()
}
fun whenSomethingGetsVerified() {
doThings()
callback.onVerified()
}
}
EDIT: forgot you are using Kotlin, changed to Kotlin implementation
You can't access the UI from a background thread, Kotlin or not. You have to run this on the UI thread:
runOnUiThread {
val checkMark: ImageView = findViewById(R.id.checkmarkImage)
checkMark.visibility = View.VISIBLE
}
I used the anko library to create a login view.
class SingInView : AnkoComponent<SingleInActivity> {
override fun createView(ui: AnkoContext<SingleInActivity>) = with(ui) {
verticalLayout {
lparams(width = matchParent, height = matchParent)
textView("Member Login")
editText {
hint = "E-mail"
}
editText {
hint = "PassWord"
}
button("Login")
}
}
}
and SingleInActivity.kt
class SingleInActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState)
SingInView().setContentView(this)
and MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startActivity(new Intent(this, SingInView.class));
finish();
}
}
current My app MainActivity -> SingleInActivity -> SingInView .
of course it can be made simply.
but there is a condition
1. MainActivity is java (kotlin prohibition)
2. use only MainActivity, SingInView.
How to solve this problem?
How to call the Anko class directly from a Java class
If you dig through the Anko source code you'll quickly find this:
interface AnkoComponent<in T> {
fun createView(ui: AnkoContext<T>): View
}
And from the wiki (where MyActivityUI is the component): MyActivityUI().setContentView(this). Now, the AnkoComponent is just an interface and the setContentView method is an extension function that returns createView.
Anyways, the setContentView extension function passes the last variable of the AnkoContextImpl as true. The last variable is whether or not to actually set the content view, which is the reason the activity is passed in the first place.
TL;DR (and possibly more sensible summary of my point):
The component is not an Activity
The setContentView method is not a replacement for setContentView in an Activity; just a wrapper for it.
And since it isn't an activity, you can't use an intent into it. And, as a result of that, you cannot use it standalone. You need an activity. Now, you can of course use the regular approach, but there's also another way. Since the AnkoComponent itself doesn't have any fields, it can be serialized without much trouble. Just to clarify: some fields can be serialized even if it isn't serializable (all though some classes like Context cannot be serialized). Anyways, you create an activity:
class AnkoComponentActivity : AppCompatActivity(){//Can be a regular Activity too
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
val component = intent.getSerializableExtra("uiComponent") as AnkoComponent<AnkoComponentActivity>//The type has to match this activity, or setContentView won't allow passing `this`
component.setContentView(this)//The context of the activity doesn't get passed until this point, which enables the use of this system.
}
}
Or it's equivalent in Java:
public class AnkoComponentActivity extends AppCompatActivity {
public void onCreate(Bundle sis){
super.onCreate(sis);
AnkoComponent<AnkoComponentActivity> component = (AnkoComponent<AnkoComponentActivity>) getIntent().getSerializableExtra("uiComponent");
org.jetbrains.anko.AnkoContextKt.setContentView(component, this);//For reference, this is how you call Kotlin extension functions from Java
}
}
Note that any UI component sent to this class has to be declared with <AnkoComponentActivity>. In addition, the components have to implement Serializable. Otherwise they can't be passed through the Bundle. Alternatively, you can use ints or Strings as identifiers and use the value to pick which AnkoComponent to show.
All though, the absolutely easiest way is just creating one activity per component.
TL;DR: AnkoComponent is not an Activity, meaning you can't use intents into it. You have to use an Activity, but using Serializable enables you to pass the component through a bundle to an Activity made for manual creation of multiple AnkoComponents without specifying specific types.