onSaveInstanceState
to onRestoreInstanceState
without serialization. It uses the writeStrongBinder(IBInder)
method of the android.os.Parcel
class.... system processes
( http://developer.android.com/intl/ru/reference/android/app/Activity.html )
Activity
to update the displayed state of the application.onClick()
) to start an asynchronous operationActivity
Activity
activity displayed at the time of completion of the operation may beActivty
from the application is displayed.Activity
.TicketSubsystem
)TicketActivity
where the ticket will be displayed and the button "Take a ticket"TicketSubsystem
component at TicketSubsystem
. Ticket could be Ticket.currentTicket
, or in the field in the heir class android.app.Application
.
from MVC
— that is, generate notifications when a ticket appears (or is replaced).TicketSubsystem
model in MVC
terms, then the Activity
will be able to subscribe to events and update the ticket display when it is loaded. In this case, the Activity
will perform the role of View
(
) in terms of MVC
.TicketSubsystem
and not take care of anything else.TicketSubsystem
. The ticket itself must also be presented somehow, let it be the Ticket
class. Both of these classes must be able to fulfill the role of an active model.PropertyChangeSupport
and PropertyChangeListener
from the java.beans
packageObservable
and Observer
from java.util
BaseObservable
and Observable.OnPropertyChangedCallback
from android.databinding
android.databinding.Bindable
annotation. But there are other ways, and they all fit.PropertyChangeSupport
from java.beans
). @groovy.beans.Bindable class TicketSubsystem { Ticket ticket } @groovy.beans.Bindable class Ticket { String number int positionInQueue String tellerNumber }
TicketActivity
(as almost all objects related to the presentation) appears and disappears at the will of the user. The application only needs to correctly display the data at the time the Activity
appears and when the data changes, while the Activity
is displayed.TicketActivity
you need:ticket
TicketSubsytem
(to refresh the view when the ticket
appears)PropertyChangeListener
from java.beans
for the sake of demonstration android.databinding
library, PropertyChangeListener ticketListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { updateTicketView(); } }; void updateTicketView() { TextView queuePositionView = (TextView) findViewById(R.id.textQueuePosition); queuePositionView.setText(ticket != null ? "" + ticket.getQueuePosition() : ""); ... }
PropertyChangeListener ticketSubsystemListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { setTicket(ticketSubsystem.getTicket()); } }; void setTicket(Ticket newTicket) { if(ticket != null) { ticket.removePropertyChangeListener(ticketListener); } ticket = newTicket; if(ticket != null) { ticket.addPropertyChangeListener(ticketListener); } updateTicketView(); }
setTicket
method when replacing a ticket removes a subscription to events from an old ticket and subscribes to events from a new ticket. If you call setTicket(null)
, then TicketActivity
unsubscribe from ticket
events. void setTicketSubsystem(TicketSubsystem newTicketSubsystem) { if(ticketSubsystem != null) { ticketSubsystem.removePropertyChangeListener(ticketSubsystemListener); setTicket(null); } ticketSubsystem = newTicketSubsystem; if(ticketSubsystem != null) { ticketSubsystem.addPropertyChangeListener(ticketSubsystemListener); setTicket(ticketSubsystem.getTicket()); } } @Override protected void onPostCreate(@Nullable Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); setTicketSubsystem(globalTicketSubsystem); } @Override protected void onStop() { super.onStop(); setTicketSubsystem(null); }
upon completion of the asynchronous operation. A
will be updated on the notification from the
. public class GetNewTicket extends AsyncTask<Void, Void, Void> { private int queuePosition; private String ticketNumber; @Override protected Void doInBackground(Void... params) { SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); Random random = new Random(); queuePosition = random.nextInt(100); ticketNumber = "A" + queuePosition; // TODO , // . return null; } @Override protected void onPostExecute(Void aVoid) { Ticket ticket = new Ticket(); ticket.setNumber(ticketNumber); ticket.setQueuePosition(queuePosition); globalTicketSubsystem.setTicket(ticket); } }
globalTicketSubsystem
link (also referred to in TicketActivity
) depends on the way subsystems are built in your application. public class ReadTicketFromFileextends AsyncTask<File, Void, Void> { ... @Override protected Void doInBackground(File... files) { // number, positionInQueue, tellerNumber } @Override protected void onPostExecute(Void aVoid) { Ticket ticket = new Ticket(); ticket.setNumber(number); ticket.setPositionInQueue(positionInQueue); ticket.setTellerNumber(tellerNumber); globalTicketSubsystem.setTicket(ticket); } }
Layers
levels, we’ll get something like this:TicketActivity
creates a GetNewTicket
- down arrow. GetNewTicket
creates a Ticket
- down arrow. Anonymous ticketListener
implements the PropertyChangeListener
interface - the down arrow. Ticket
notifies listeners PropertyChangeListener
- down arrow. Etc.Domains
level reflect business entities with which the application works. They should be independent of how our application is organized. For example, the presence of the positionInQueue
field of a Ticket
is due to business requirements (and not the way we wrote our application).Application
level is the boundary of where the application logic can be located (except for the appearance formation). If you need to do some useful work, the code should be here (or below).Presentation
level. So this class can contain only the mapping code, and no logic. For logic, he will have to access classes from the Application
level.Layers
is conditional. The class is at a given level as long as it fulfills its requirements. That is, as a result of the editing, the class may move to another level, or become unsuitable for one level.android.databinding
and roboguice
. Look at the code, and here I briefly explain what choice I made and for what reasons.com.android.support:appcompat-v7
dependency is added because commercial development relies on this library to support older android versions.com.android.support:support-annotations
dependency is added to use the @UiThread
annotation (there are many other useful annotations).org.roboguice:roboguice
is a library for dependency injection. Used to compose an application from parts using Inject annotations. Also, this library allows you to embed resources, links to widgets and contains a mechanism for sending messages similar to CDI Events from JSR-299.TicketActivity
using annotation @Inject
receives a link to TicketSubsystem
.ReadTicketFromFile
task using the @InjectResource
annotation @InjectResource
file name from the resources from which the ticket is to be loaded.TicketSubsystem
using @Inject
gets a Provider
that it uses to create a ReadTicketFromFile
.org.roboguice:roboblender
creates a database of all annotations for org.roboguice:roboguice
at compile time, which is then used at run time.app/lint.xml
with settings for suppressing warnings from the roboguice
library.dataBinding
option in app/build.gradle
enables special syntax in layout files similar to Expression Language
( EL
) and includes the android.databinding
package, which is used to make Ticket
and TicketSubsystem
an active model. As a result, the presentation code is greatly simplified and replaced with declarations in the layout file. For example: <TextView ... android:text="@{ts.ticket.number}" />
.idea
folder is in .gitignore
to use any version of Android Studio
or IDEA
. The project is perfectly imported and synchronized via the build.gradle
files.gradlew
, gradlew.bat
and the folder gradle
). This is a very effective and convenient mechanism.unitTests.returnDefaultValues = true
in app/build.gradle
. This is a trade-off between protection against random errors in unit tests and the brevity of unit tests. Here I prefer the shortness of the unit tests.org.mockito:mockito-core
library is used to create stubs in unit tests. In addition, this library allows you to describe “System Under Test” using the annotations @Mock
and @InjectMocks
. When using Dependency Injection, components “expect” that they will be injected dependencies before using them. Before the tests also need to implement all the dependencies. Mockito
can create and implement stubs in the class under test. This greatly simplifies the test code, especially if the fields being injected have limited visibility. See GetNewTicketTest.Mockito
, and not Robolectric
?org.powermock:powermock-module-junit
library org.powermock:powermock-module-junit
and org.powermock:powermock-api-mockito
. Some things cannot be replaced with plugs. For example, replace the static method or suppress the call to the base class method. For these purposes, PowerMock
replaces the class loader and corrects the byte code. In TicketActivityTest
, using PowerMock
suppresses the call to RoboActionBarActivity.onCreate(Bundle)
and sets the return value from the call to the static method DataBindingUtil.setContentView
LogInterface log
variable not static?LogInterface
and LogImpl
which are just descendants of similar classes from RoboGuice?@ImplementedBy(LogImpl.class)
.@UiThread
annotation for the Ticket
and TicketSubsystem
?onPropertyChanged
events that are used in UI components to update the display. It is necessary to ensure that calls will be made in the UI stream.TicketSubsystem
constructor?TicketSubsystem
is created (only one copy is created, TicketSubsystem
it is marked with the @Singleton
annotation). However, in the TicketSubsystem
constructor, TicketSubsystem
cannot create a ReadTicketFromFile
, since it needs a link to a not yet created TicketSubsystem
. Therefore, the creation of ReadTicketFromFile
postponed to the next UI flow cycle.adb shell am kill ru.soldatenko.habr3
Source: https://habr.com/ru/post/281290/
All Articles