🚀 Easy and intuitive pattern library for Android.
MVP and MVVM use Fragment or Activity as "VIew"
So when we write code inside the Fragment or Activity, codes are getting mixed with
"View" code and "Screen Code" such as onCreate,onCreateView, onViewCreated
or onSaveInstanceState, onRestoreInstanceState
.onActivityResult
etc.
It makes hard to see each "View Logic" and "Screen Logic"
And the most important thing is that "Business Logic" can be included in the "View"(Fragment, Activity) easily.
It's because Activity, Fragments are actually "Control Tower" (control lifecycle and receives view events)
SVC gives the way how to divide Screen, View and Business Logic.
For more read check article below
-
4 reasons why MVP is not good enough https://medium.com/@bansooknam/intro-svc-the-better-pattern-against-mvp-138e6e790bbc
-
Detail concept of SVC https://medium.com/@bansooknam/svc-the-better-pattern-against-mvp-66e6d342a23f
Ps. It can be used with ViewModel as Well.
you can check Examples here below.
https://github.com/BansookNam/android-architecture
https://github.com/BansookNam/svc-lotto
- For activity and fragment for the screen, should inherit the
SVC_{name of screen class}
class. - Add screen annotations
SvcActivity
,SvcFragment
,SvcDialogFragment
. - And declare
RequireControlTower
,RequireViews
annotation above the class. - Build you project, then
SVC_{YourScreen}
class will be generated automatically by svc processor.
@SvcActivity
@RequireViews(MainViews::class)
@RequireControlTower(MainControlTower::class) // magic things do happening
class MainActivity : SVC_MainActivity() { // SVC_MainActivity will be generated after build project.
//extend SVC_{name of class}
}
@SvcFragment
@RequireViews(StatisticViews::class)
@RequireControlTower(StatisticControlTower::class) // magic things do happening
class StatisticFragment : SVC_StatisticFragment() { // SVC_StatisticFragment will be generated after build project.
//extend SVC_{name of class}
}
@SvcDialogFragment
@RequireViews(SampleActionViews::class)
@RequireControlTower(SampleActionControlTower::class)
@RequireListener(SampleActionDialogListener::class) //listener from another screen.
class SampleActionDialog : SVC_SampleActionDialog() { // SVC_SampleActionDialog will be generated after build project.
//extend SVC_{name of class}
}
- For controlTower, should inherit the
SVC_{name of controlTower class}
class. - Declare annotations
ControlTower
. - Declare
RequireScreen
,RequireViews
annotation above the class. - Build you project, then
SVC_{YourControlTower}
will be generated automatically by svc processor.
@ControlTower
@RequireViews(StatisticViews::class)
@RequireScreen(StatisticFragment::class) //screen which this controlTower will be used.
class StatisticControlTower : SVC_StatisticControlTower(), StatisticViewsAction {
//extend SVC_{name of class}
//implement ViewsAction which 'Views' is needed.
}
@ControlTower
@RequireViews(CommonViews::class)
@RequireScreen(CommonScreen::class) //abstract screen is available to declare. (CommonScreen is interface)
class CommonControlTower : SVC_CommonControlTower(), CommonViewsAction {
//extend SVC_{name of class}
//implement ViewsAction which 'Views' is needed.
}
You can really divide "Views" from Activity and Fragment.
so you can see logic about real Fragment and Activity very well like below. (source link)
@SvcActivity
@RequireViews(TaskDetailViews::class)
@RequireControlTower(TaskDetailCT::class)
class TaskDetailActivity : SVC_TaskDetailActivity() {
var taskId: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
/**
* we should set taskId before super onCreate
* because createControlTower will be called on super.onCreate()
* and taskId is non null String type in constructor of TaskDetailCT
*/
taskId = intent.getStringExtra(EXTRA_TASK_ID)
super.onCreate(savedInstanceState)
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val deletePressed = item.itemId == R.id.menu_delete
if (deletePressed) ct.deleteTask()
return deletePressed
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.taskdetail_fragment_menu, menu)
return true
}
fun startEditTastActivity(taskId: String) {
val intent = Intent(this, AddEditTaskActivity::class.java)
intent.putExtra(AddEditTaskActivity.ARGUMENT_EDIT_TASK_ID, taskId)
startActivityForResult(intent, REQUEST_EDIT_TASK)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_EDIT_TASK) {
// If the task was edited successfully, go back to the list.
if (resultCode == Activity.RESULT_OK) {
setResult(Activity.RESULT_OK)
finish()
}
}
}
fun finishAfterDelete() {
setResult(Activity.RESULT_OK)
finish()
}
companion object {
const val EXTRA_TASK_ID = "TASK_ID"
const val REQUEST_EDIT_TASK = 1
}
}
Each alphabet stands for.
S — Screen
V — Views
C — Control Tower
+In addition
ViewsAction — Contains user interaction method which "Views" can produce. and Control Tower should know (such as click, swipe, drag..)
- Each "Screen" has "Views" and "ControlTower".
- "Views" don't know "ControlTower" directly. It knows "ControlTower" as "ViewsAction"
- "ControlTower" knows "Views"'s public methods and fields.
- Each "Views" and "ControlTower" has "Screen"
When we use MVP or MVVM, "View"(which is implemented on Activity, Fragment) can easily get Bigger with lots of responsibility. It has 3 main responsibility.
- Screen logics
- View logics
- Control tower logics (access directly to Mediator "Presenter" or "ViewModel")
And both MVP, MVVM can call Business logics of Mediator directly.
As same as MVP, Mediator("ControlTower") comunicated with Model.
It's exactly same.
We can use ViewModel in ControlTower to take advantage of "Auto Lifecycle Management".
We use ControlTower
as Mediator and ViewModel
as data holder.
In this type, ControlTower
has Repositories
and manage the datas
Similar with type1, however in this architecture ViewModel
has Repositories
and manage the data.
You can design in type2 in case of Repository
can independently divided with ControlTower
.
ViewModel
will contain data change methods.
There are 2 big differences.
- SVC divides "View"'s 3 responsibilities into 3 parts, which is Screen, ControlTower, Views
- "Views" cannot call "Control Tower" directly. (Because "Views" knows "ControlTower" as "ViewsAction")
With this 2 big differences. We can
- Prevent "View" from being "God View"
- We can see well divided logics.
- When we write "View" logic we don't need to think about business logics.
- We don't need to make duplicated methods by interfaces (compare to MVP)
- We don't need to observe commands with parameter (compare to MVVM)
- We can use same "Views" in "A Activity" and "B Fragment". (reusable. ex-
CommonViews
from sample) - We can understand intuitively with clear naming.
Packages are available in jcenter
Include below in your top build.gradle file
allprojects {
repositories {
jcenter() //add this line
}
}
Include below in your "application" build.gradle file
apply plugin: 'kotlin-kapt'
implementation "com.naver.android.svc:svc:1.0.0"
kapt "com.naver.android.svc:svc-compiler:1.0.0"
Github Link: https://github.com/naver/svc-template
If you want to create Activity, Fragment, DialogFragment quickly. Try SvcTemplate.
-
clone
https://github.com/naver/svc-template.git
-
run shell script through command line. (Terminal in mac)
./install.sh
-
restart Android Studio
-
write screen name, author then click "Finish".
You will see "Unresolved Reference" error. -
click "Build" - "Rebuild Project" This will create "SVC_{component}" based on annotations.
-
**Done! Happy coding!
"Views" and "ControlTower" can be reused in different Screens. (It means it has same look or same viewsAction and proccess)
-
Views you can easily reuse "Views" in this pattern
-
ControlTower If you want to reuse you should implement your screen as "Screen"or make "Abstract Screen (implements Screen)" with common methods then refer the "Abstract Screen" on your reused "Views" or "ControlTower".
SVC is licensed under the Apache License, Version 2.0. See LICENSE for full license text.
Copyright 2018 NAVER Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.