The nonsense written in front:
I’m an Android programmer, though it’s been two years since I last wrote about Android. This time, I temporarily changed the original mixed development plan to native development, and left the work of Android coding to me.
It took me a month to completely rewrite the logic I had implemented with ReactNative and H5. In the second week, I got to know Databinding and Jetpack and completely changed my android coding habits, so I went back and rebuilt all the code and constantly corrected my previous mistakes.
While YOU’re at it, I’m going to take a look at the evolution of the process and record the last four weeks of potholes, and also sort out the components and classes you used in case you need to write it again.
So, here we go.
Painful MVX
While front-end or mobile technology has evolved rapidly in recent years, what engineers do hasn’t changed much: build a complete view of dynamic data and static pages, while minimizing the coupling between the two in the process.
The early Android system still follows the MVC idea of the server side, instantiating the view layer components in the Controller and assembling the corresponding data. This is a crude and sensible approach (in the case of simple interactions), but the downside is obvious: an Activity as a Controller is always bloated.
Even with the ButterKnife annotation library and the evolution of the so-called MVX MVP, the pain point of the MVX architecture was that XML, as the View layer, was always static.
$(‘div’).onclick = function() {}); Now, however, Angular, React, and Vue can write variables into the View layer, giving the View itself the part of the View that the Controller must take care of.
God’s for God, Caesar’s for Caesar, and the view layer and the data layer have suddenly become distinct.
Android developers have waited years for databinding.
In fact, I started using DB 2 years ago, but at that time, ON the one hand, DB was not very stable, on the other hand, it was not that powerful. Of course, the most important thing is that the first time I used DB, I crashed the whole project, the other people in the team synchronized my code and couldn’t compile it anyway (gradle’s fault). Then MY boss kicked me into the front office to “help out” for more than two years.
Aha, Databinding!
Take an example to illustrate the huge difference between db and no DB.
This is a basic interface of the car card, above is the basic information of the character, below is the basic attributes of the character, click the random button can be initialized with the throw point method.
If there is no db, it is written as follows:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = findViewById(R.id.recyclerView);
TextView tvName = findViewById(R.id.tv_name);
TextView tvRace = findViewById(R.id.tv_race);
TextView tvLevel = findViewById(R.id.tv_level);
TextView tvClass = findViewById(R.id.tv_class);
GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
ArrayList<HeroAttr> arrayList = new ArrayList<>();
AttrAdapter attrAdapter = new AttrAdapter();
attrAdapter.initData(arrayList);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(attrAdapter);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } }); }}Copy the code
There’s no need to look because this is a wrong demonstration (dog head)
Without any logical coding at the data layer, the Activity is already flooded with view instances. Dragging things from the View layer to the data layer to instantiate and assign values is the most painful part of MVX.
But databinding’s implementation and mindset are the opposite.
Open the databinding
Open build.gradle in your app folder and add the following code
compileSdkVersion 27
// ...
dataBinding {
enabled = true
}
// ...
defaultConfig {
}
Copy the code
However, it seems that the current function is not particularly stable, and Google has made several changes to DB. When using DB in the new project, it took more than a day to step on the pit, and the most reported mistakes are:
Element: class com.intellij.psi.impl.source.xml.XmlFileImpl because: different providers:
Copy the code
This or other compilation errors can be resolved as follows:
- Gradle compilation failures can be solved on at least 70% of cases.
- Upgrading to the version of Android Studio with 2.x was very difficult at first, and Gradle was plagued with compilation errors. This happened a lot less when I upgraded to 3.1. I now use 3.3 Canary, which gradle uses more aggressively
'com. Android. Tools. Build: gradle: 3.3.0 - alpha03'
There are fewer bad things. gradle.properties
Add the following code to the file:
android.databinding.enableV2=true
Copy the code
The argument on StackOverflow is that Google has buried two versions of databinding that are incompatible with each other and need to force the higher version on.
Sometimes br.xx error, if you look carefully, there will be two BR files, I usually use the second end of the adapter can be accessed normally.
- build -> make project
- file -> invalit cache and restart
Several pit points:
- Compiling error.
There may be XML or code written wrong, now THE DB prompt to do pretty conscience, according to the picture of the general can be successfully solved.
- After wrapping the XML with the Layout tag, there is no Binding class for using databingUtils in your code
File -> invalit cache and restart rebuild
- Binding does not provide the corresponding set method when variables are added using the varable tag.
Use setVariable(br. XXX, obj) instead.
Java 8 and LAMda expressions
There are a lot of amazing new features in Java 8, but the one that really intriges me is the lamda expression. Writing LAMda expressions gives me the familiarity of writing front-end arrow functions and gives OOP in Java a touch of functional programming.
Interestingly, my front-end colleagues understood lamda expressions at a glance, while android colleagues found it awkward.
Simply put, if there is one and only one abstract method for interface A, it can be written directly as a LAMda expression. (It is said that Java 8 interfaces can write concrete methods as well as abstract methods, and are increasingly procedural oriented)
That is for
public interface A {
int func(B b)
}
Copy the code
A a = new A() { int func(B b) { // ... Own logic}}Copy the code
Is equivalent to
A a = b -> {
//...
}
Copy the code
It also means the classical way of writing:
view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // ... }});Copy the code
Is equivalent to:
view.setOnClickListener(v -> {
// ...
});
Copy the code
Of course, it looks like it’s simply a little less code, and it is — a neat way to write anonymous functions. But the addition of databinding cured a programming mistake I’d been making for years.
I don’t know. It says in the back.
Awesome databinding
After refactoring using databinding and lamda expressions, the code in MainActivity looks like this:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); / / to databinding layout change ActivityMainBinding viewDataBinding = DataBindingUtil. The setContentView (this, R.layout.activity_main); // Set basic information Hero = new Hero(Ziegfeld."Human"."Level 1"."Master");
viewDataBinding.setVariable(BR.hero, hero);
View.OnClickListener listener = v -> {
int[] values = new int[6];
for(int i=0; i< 6; i++) { values[i] = getFinalValue(); } / / random to properties after each click copy viewDataBinding setVariable (BR) heroAttrs, heroAttrs getHeroAttrs (values)); }; / / set the listener viewDataBinding. SetVariable (BR. The listener, the listener); } // get the property private intgetFinalValue() {
Double[] values = {
new Double(Math.floor(Math.random() * 6 + 1)),
new Double(Math.floor(Math.random() * 6 + 1)),
new Double(Math.floor(Math.random() * 6 + 1)),
new Double(Math.floor(Math.random() * 6 + 1))
};
ArrayList<Double> array = new ArrayList<>();
Collections.addAll(array, values);
Collections.sort(array);
array.remove(0);
int finalValue = 0;
for(int j = 0; j < array.size(); j++) { finalValue += array.get(j); } // Individual room rules: a single attribute must not be less than 6returnfinalValue > 6 ? finalValue : getFinalValue(); }}Copy the code
Didn’t.
It’s really gone, with the business logic, there’s only so much code, and you can’t see any view instances in the entire Activity.
This is the great thing about Databinding.
I started with DB as just another butterknife and wrote code like this:
viewDataBinding.tvName.setText(Ziegfeld);
viewDataBinding.tvRace.setText("Human");
viewDataBinding.tvClass.setText("Master");
viewDataBinding.tvLevel.setText("Level 1");
Copy the code
But it’s still the old MVC mentality of “bringing an instance of a View into an Activity.” This use of databinding can’t be wrong, but it’s all anti-aircraft guns shooting mosquitoes.
From a DB perspective, there should be no view instances in an activity at all. If it does, the encapsulation is not thorough enough.
The entire code does three things:
- Dbify the activity:
ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
- Initialize the basic character card information and throw it into the XML
viewDataBinding.setVariable(BR.hero, hero);
- Initialize the listener that assigns values to character attributes and throw it into the XML
viewDataBinding.setVariable(BR.listener, listener);
- Every time you get a random attribute, you throw it into the XML
viewDataBinding.setVariable(BR.heroAttrs, HeroAttrs.getHeroAttrs(values) );
To the end.
The biggest change MainActivity has made since the introduction of databinding is its shift from a data handler to a data provider. In the old days, an Activity initializes an instance of a view and assigns a value to the instance. The assignment logic has to be implemented (in the absence of a presenter). Now the Activity is just a server, data and so on.
To whom?
XML.
In the XML databinding
Databinding’s XML is specially processed and differs from traditional XML in the following ways:
- The root layout of the original interface layer needs to be added to the outer layer
layout
The label. - If you want to introduce variables, you need to add
data
The label,data
The label is the same as the root layout, and there can only be one. data
There can be many inside the tagvariable
Tag, must havename
Attributes (unique within the XML layout) andtype
Attribute (which may not be unique in XML, but is slightly less generic), used to determine unique variables.- XML layout can be used
@ {}
Refer directly to variables defined in the data tag (a familiar move).
<? xml version="1.0" encoding="utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android"> <! -- Here is the variable definition --> <! --viewDataBinding.setVariable(BR.heroAttrs, xxx ); -- > <! --BR. <data> <variable name="heroAttrs"
type="android.powerword.siegfried.com.dnd_builder.model.HeroAttrs" />
<variable
name="hero"
type="android.powerword.siegfried.com.dnd_builder.model.Hero" />
<variable
name="listener"
type="android.view.View.OnClickListener"/> </data> <! - specific view - > <. Android support. The constraint. ConstraintLayout XMLNS: app ="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorWhite"
tools:context=".MainActivity">
<RelativeLayout
android:id="@+id/rl_hero_short"
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="@color/colorPrimaryDark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"> <! <LinearLayout Android :layout_width= <LinearLayout Android: Layout_width ="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center_horizontal"
android:orientation="vertical">
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="32dp"
android:elevation="10dp"
android:src="@drawable/ic_launcher_background" />
<LinearLayout
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{hero.name}"
android:textColor="@color/colorWhite" />
<TextView
android:id="@+id/tv_race"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="@{hero.race}"
android:textColor="@color/colorWhite" />
<TextView
android:id="@+id/tv_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="@{hero.level}"
android:textColor="@color/colorWhite" />
<TextView
android:id="@+id/tv_class"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="@{hero.clazz}"
android:textColor="@color/colorWhite" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
<android.powerword.siegfried.com.dnd_builder.custom.AttRecycleView
android:id="@+id/recyclerView"
style="@android:style/Widget.Material.TextView"
android:layout_width="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rl_hero_short"
app:heroAttrs="@{heroAttrs}"
app:layout_constraintVertical_weight="1"
android:layout_height="wrap_content" />
<android.powerword.siegfried.com.dnd_builder.custom.DrawableButton
android:id="@+id/button"
style="@android:style/Widget.Material.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryDark"
android:text="Random"
android:drawableTint="@color/colorWhite"
android:drawableLeft="@drawable/baseline_apps_24"
android:textColor="@color/colorWhite"
android:onClick="@{listener}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/recyclerView"
app:layout_constraintVertical_bias="1.0" />
</android.support.constraint.ConstraintLayout>
</layout>
Copy the code
What front-end engineers and mobile engineers have been doing is separating the dynamic data from the static view while minimizing the coupling between the two in the process.
Before databinding, you could only do this by instantiating the View in the Controller and assembling the data; With databinding, dynamic data (and the means to manipulate it) can be provided directly to the View layer. The original view layer in the Controller is completely returned to the view itself, just need to focus on providing the data required by the View layer.
This practice is said to be called data binding.
When I first started writing XML in DB, I was a bit dazed. It felt like I was drawing Vue with a Jade template – {{}} became @{}.
Draw a component, leave a pit for dynamic data, draw another component, leave another pit, and repeat. After the interface is drawn, write data in data, write methods in method, and fill holes with data and methods.
Databinding expressions
Databinding can use expressions in XML, such as > < = or ternary expressions, as well as some degree of logical processing. However, after writing for more than a month, I think the following three writing methods are most commonly used.
1. An attribute of the set method has been assigned
for
<data> <! -... --> <variable name="hero"
type="android.powerword.siegfried.com.dnd_builder.model.Hero" />
</data>
Copy the code
You can directly use: “@{hero.name}” to assemble data
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{hero.name}"
android:textColor="@color/colorWhite" />
Copy the code
The same goes for Level Class race.
For Android :text=”@{hero.name}”, this is equivalent to textView.settext (hero.name). Db checks the set method of the TextView component’s property text and determines if the parameter is a String (the name attribute of the Hero object is String). If all of the above conditions are met, DB calls the component’s setText method. Hero.name is passed in as an argument.
AKA, reflection…
2. Assign to attributes that do not have a set method or are not included.
When it comes to app:heroAttrs=”@{heroAttrs}”, things get a little more complicated.
First of all, the component that loads heroAttrs data is a RecycleView, and the RecycleView doesn’t have heroAttrs properties or setHeroAttrs methods.
Second, data is linked through Adapter and RecycleView. Adapter is not a component.
At the beginning I was in this blind spot of thinking, into the tip of the ox can not come out. However, when I thought about my previous point of view, the answer was obvious.
There isn’t a RecycleView, so write a RecycleView based component.
public class AttRecycleView extends RecyclerView {
private AttrAdapter attrAdapter;
public AttRecycleView(Context context) {
this(context, null, 0);
}
public AttRecycleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AttRecycleView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
initHeroAttrs();
}
private void init() {// create a RecycleView // create an adatper/manager // GridLayoutManager layoutManager = new GridLayoutManager(this.getContext(), 2); attrAdapter = new AttrAdapter(); this.setLayoutManager(layoutManager); this.setAdapter(attrAdapter); } public voidsetHeroAttrs(HeroAttrs heroAttrs) {
if(heroAttrs == null) {
return ;
}
this.setHeroAttrs(heroAttrs.attrs);
}
private void initHeroAttrs() {
HeroAttrs attrs = HeroAttrs.getInitHeroAttrs();
this.setHeroAttrs(attrs);
}
private void setHeroAttrs(ArrayList<HeroAttrItem> arrayList) { this.attrAdapter.initData(arrayList); }}Copy the code
This is also the bad habit of MVC before, both adapter and manager are used to write in the Activity. But in fact, adapter and manager were originally supposed to follow RecycleView, and RecycleView exposed only as a way to load data.
You don’t care what you do with the data, just give it to me — this kind of thinking is now said to be called responsive.
React. I don’t believe it’s a coincidence.
3. Use lamda expressions to set up functions
The idea of MVX involves not only data that needs to be manipulated, but also operations on data — so-called functions. However, Java is not JS and cannot directly manipulate functions (you cannot use functions as arguments, return values cannot be functions and therefore there are no higher-order functions). Therefore, in method passing, the more common approach is to use a class to implement the corresponding method interface, and then take the interface as a parameter.
The biggest impression I have of classes and interfaces is that classes are used to describe properties and interfaces are used to describe methods.
Like the classic view. OnClickListener, the most straightforward approach is
View.OnClickListener listener = new View.OnClickListener(){ public void onClick(View view) { .... }}Copy the code
<data>
<variable
name="listener"
type="android.view.View.OnClickListener"/> </data> <Button <! Android :onClick="@{listener}"
/>
Copy the code
Of course, I can’t go wrong with this, but I prefer the lamda expression.
Because it’s the same as the front end.
If there is a class presenter as follows
public class Presenter {
private final ViewDataBinding viewDataBinding;
public Presenter(ViewDataBinding viewDataBinding) {
this.viewDataBinding = viewDataBinding;
}
public void onClick() {
int[] values = new int[6];
for(int i=0; i< 6; i++) { values[i] = getFinalValue(); } viewDataBinding.setVariable(BR.heroAttrs, HeroAttrs.getHeroAttrs(values) ); } private intgetFinalValue() {
Double[] values = {
new Double(Math.floor(Math.random() * 6 + 1)),
new Double(Math.floor(Math.random() * 6 + 1)),
new Double(Math.floor(Math.random() * 6 + 1)),
new Double(Math.floor(Math.random() * 6 + 1))
};
ArrayList<Double> array = new ArrayList<>();
Collections.addAll(array, values);
Collections.sort(array);
array.remove(0);
int finalValue = 0;
for(int j = 0; j < array.size(); j++) { finalValue += array.get(j); }returnfinalValue > 6 ? finalValue : getFinalValue(); }}Copy the code
then
View.OnClickListener listener = new View.OnClickListener(){public void onClick(View View){dice.... }}Copy the code
Is equivalent to
View.OnClickListener listener = View -> {// dice.... }}Copy the code
Is equivalent to
View.OnClickListener listener = view -> presenter.onClick(view);
Copy the code
then
<data>
<variable
name="listener"
type="android.view.View.OnClickListener"/> </data> <Button <! Android :onClick="@{listener}"
/>
Copy the code
Is equivalent to
<data>
<variable
name="presenter"
type="Presenter"/> </data> <Button <! Android :onClick="@{ view -> presenter.onClick(view)}"
Copy the code
And MainActivity now only has this:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
Hero hero = new Hero(Ziegfeld."Human"."Level 1"."Master"); viewDataBinding.setVariable(BR.hero, hero); Presenter presenter = new Presenter(viewDataBinding); viewDataBinding.setVariable(BR.presenter, presenter); }}Copy the code
Where is all the logic?
Throw it to Presenter.
This is also a bad example because, from a development point of view, a presenter should not have anything to do with a View. A Presenter is only responsible for processing data and is isolated from the view. It’s the liveData in the ViewModel that connects the data to the view.
There are two advantages to writing this way:
- Less code, that’s pretty obvious. (Not much less, to be honest).
- This writing of db forces developers to put heavy logic in P, which directly reduces the size of the Activity (which is critical).
In other words, DB forces developers to use at least MVP’s level of decoupling.
In my previous coding habits, writing logic in a listener was quite common. On the one hand, it’s easy (because the data is in the activity), and on the other hand, it’s lazy (not bothering to optimize the logic).
However, from a development perspective, if all the data processing logic is stacked randomly in an Activity, it is easy to write messy spaghetti code; The modularity of business logic is greatly enhanced by categorizing all concrete logic into a Presenter — an Activity that is merely a heap P container. Logics are independent of each other, and any change in logic can be done by modifying the presenter.
To be honest, WHEN I started writing MVP, I had no idea what P was. Then WHEN I wrote Angular, I realized that a Presenter is a Service….
The biggest benefit of putting data and logic declarations into XML as variables is knowing what the page does without having to look at the code and flip through the XML. At the same time, once you receive a request, simply evaluate which data is dynamic and which methods are used to process the data. You can guess seven or eight for Activity and XML.
4 Bind adapter
I haven’t used it directly, but I looked at the documentation and it actually addresses type 2 and 3 problems. (Please point out the application scenario)
Databinding and responsiveness
I’m an Android programmer, although my coding habits are completely front-end. In the past month, I have been developing apps with DB and Jetpack modules without too much pressure. I even felt like I was writing the front end of the app. The syntax was changed and the writing method was changed, but the principle and core idea were the same as vue/ React. Especially when I try koltinization).
More than two years of front-end coding work has brought me the biggest change from MVC thinking to reactive (and perhaps functional) habit. The code focuses on componentization (instead of instantiating the View for data assembly as before) and provides an external invocation interface, with the specific logic implemented inside the component. An Activity (or Fragment) is a container that is responsible for passing data and methods that process it.
The problem with componentized development, however, is how to gracefully transfer values between components — parent-child, flat-level, and so on.
The front-end approach is fairly uniform — create one/several data warehouses (a concept I learned while writing the front-end). React’s Redux/Mobx, Vue’s Vuex (Angular’s Service is half of it). A component can link to a data warehouse for reading and writing data to complete the transfer of values.
Jetpack also provides Android with its own data warehouse, the ViewModel.