使用 MVP 架构在运行时检查权限的最佳方法是什么?
What's the best way to check for permissions at runtime using MVP architecture?
我正在开发一个 android 应用程序,我必须在运行时请求权限。我想知道使用 Model-View-Presenter 架构实现它的最佳方法。
我最初的想法是让演示者调用负责权限的组件(比如 PermissionHandler
),并相应地更新视图。
问题在于检查权限的代码与 Activity class 紧密耦合。以下是一些需要 Activity 或上下文的方法:
ContextCompat.checkSelfPermission()
ActivityCompat.shouldShowRequestPermissionRationale()
ActivityCompat.requestPermissions()
onRequestPermissionsResult()
(回调)
这意味着我必须将一个 activity 对象传递给演示者,我不太喜欢这样,因为我听说让你的演示者不受 Android 代码的影响对于测试。
因此,我随后考虑在视图级别处理权限(在 activity 中),但后来我想这会损害让视图仅负责 UI 更新的目的, 没有业务逻辑。
我不确定解决该问题的最佳方法是使代码尽可能分离和可维护。有什么想法吗?
我会做的是:
视图将实现:
public Activity getViewActivity();
演示者将实施:
public void requestPermissions();
public void onPermissionsResult();
在requestPermissions
里面,演示者会做:getViewActivity().checkSelfPermission; getViewActivity.requestPermissions(); etc.
视图将在 onRequestPermissionsResult
回调中调用 presenter.onPermissionsResult();
有了这个,所有的逻辑都将在演示者内部实现。
在我看来,您的 Presenter 是解耦的:它不依赖于任何视图实现(它只依赖于视图界面)。
"I've heard that keeping your presenter free from Android code is good for testing."这部分没看懂。如果代码不错,可以测试没有问题。
如果您仍然希望能够模拟权限 access/requests,您仍然可以创建类似 PermissionHandler
的内容,但只能在您的视图中引用它 class。例如-
接口:
public interface PermissionsHandler {
boolean checkHasPermission(AppCompatActivity activity, String permission);
void requestPermission(AppCompatActivity activity, String[] permissions, int requestCode);
}
生产实施:
public class PermissionsHandlerAndroid implements PermissionsHandler {
@Override
public boolean checkHasPermission(AppCompatActivity activity, String permission) {
return ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED;
}
@Override
public void requestPermission(AppCompatActivity activity, String[] permissions, int requestCode){
ActivityCompat.requestPermissions(activity, permissions, requestCode);
}
}
模拟 class(例如,测试并确保您的 activity 正确处理 onRequestPermissionsResult
)
public class PermissionsHandlerMocked implements PermissionsHandler {
@Override
public boolean checkHasPermission(AppCompatActivity activity, String permission) {
return false;
}
@Override
public void requestPermission(AppCompatActivity activity, String[] permissions, int requestCode){
int[] grantResults = new int[permissions.length];
for (int i = 0; i < permissions.length; i++) {
grantResults[i] = PackageManager.PERMISSION_GRANTED
}
activity.onRequestPermissionResult(requestCode, permissions, grantResults);
}
}
然后在你的 activity:
PermissionsHandler permissionsHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
permissionsHandler = Injection.providePermissionsHandler();
//or however you choose to inject your production vs mocked handler.
}
//method from your view interface, to be called by your presenter
@Override
void requestLocationPermission() {
permissionsHandler.requestPermision((AppCompatActivity) this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_CODE_LOCATION};
}
fobo66,你总是可以让视图实现更通用的方法,比如
checkLocationPermissionGranted()
和 requestLocationPermission()
。然后您的视图实现可以根据需要引用 activity,而您的演示者永远不必触及 activity 引用。
权限请求和状态是视图(片段或 Activity)的责任,因为取决于用户发出请求或授予权限的操作。我使用 MVP 管理权限如下(读取外部存储示例):
我的合同
interface View {
...
void requestReadPermission();
boolean areReadPermissionGranted();
void showPermissionError();
void hidePermissionError();
...
}
interface Presenter {
...
void setReadPermissions(boolean grantedPermissions);
...
}
interface Model {
...
}
我的观点实现。 (在这种情况下是片段,但它可以是 Activity 或其他任何内容,Presenter 将只期望响应)。
public class MyView extends Fragment implements Contract.View {
...
Contract.Presenter presenter;
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
boolean grantedPermissions = (grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED);
presenter.setReadPermissions(grantedPermissions);
}
@Override
public void showPermissionError() {
// Show not permission message
}
@Override
public void hidePermissionError() {
// Hide not permission message
}
@Override
public void requestReadPermission() {
this.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}
@Override
public boolean areReadPermissionGranted() {
return ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}
...
以及 Presenter 实现
public class MyPresenter implements Contract.Presenter {
...
Contract.View view;
public void doSomethingThatRequiresPermissions() {
...
if ( !view.areReadPermissionGranted() ) {
view.requestReadPermission();
view.showPermissionError();
} else {
view.hidePermissionError();
doSomethingWithPermissionsGranted();
}
...
}
@Override
public void setReadPermissions(boolean grantedPermissions) {
if( grantedPermissions ){
view.hidePermissionError();
doSomethingThatRequiresPermissions();
} else {
view.showPermissionError();
}
}
public void doSomethingWithPermissionsGranted(){
...
}
然后你可以像
那样做单元测试
Contract.View mockedView;
@Test
public void requestAlbumListWithoutPermissions() {
when(mockedView.areReadPermissionGranted()).thenReturn(false);
presenter.doSomethingWithPermissionsGranted();
verify(mockedView).showPermissionError();
verify(mockedView).requestReadPermission();
}
我正在开发一个 android 应用程序,我必须在运行时请求权限。我想知道使用 Model-View-Presenter 架构实现它的最佳方法。
我最初的想法是让演示者调用负责权限的组件(比如 PermissionHandler
),并相应地更新视图。
问题在于检查权限的代码与 Activity class 紧密耦合。以下是一些需要 Activity 或上下文的方法:
ContextCompat.checkSelfPermission()
ActivityCompat.shouldShowRequestPermissionRationale()
ActivityCompat.requestPermissions()
onRequestPermissionsResult()
(回调)
这意味着我必须将一个 activity 对象传递给演示者,我不太喜欢这样,因为我听说让你的演示者不受 Android 代码的影响对于测试。
因此,我随后考虑在视图级别处理权限(在 activity 中),但后来我想这会损害让视图仅负责 UI 更新的目的, 没有业务逻辑。
我不确定解决该问题的最佳方法是使代码尽可能分离和可维护。有什么想法吗?
我会做的是:
视图将实现:
public Activity getViewActivity();
演示者将实施:
public void requestPermissions();
public void onPermissionsResult();
在requestPermissions
里面,演示者会做:getViewActivity().checkSelfPermission; getViewActivity.requestPermissions(); etc.
视图将在 onRequestPermissionsResult
回调中调用 presenter.onPermissionsResult();
有了这个,所有的逻辑都将在演示者内部实现。
在我看来,您的 Presenter 是解耦的:它不依赖于任何视图实现(它只依赖于视图界面)。
"I've heard that keeping your presenter free from Android code is good for testing."这部分没看懂。如果代码不错,可以测试没有问题。
如果您仍然希望能够模拟权限 access/requests,您仍然可以创建类似 PermissionHandler
的内容,但只能在您的视图中引用它 class。例如-
接口:
public interface PermissionsHandler {
boolean checkHasPermission(AppCompatActivity activity, String permission);
void requestPermission(AppCompatActivity activity, String[] permissions, int requestCode);
}
生产实施:
public class PermissionsHandlerAndroid implements PermissionsHandler {
@Override
public boolean checkHasPermission(AppCompatActivity activity, String permission) {
return ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED;
}
@Override
public void requestPermission(AppCompatActivity activity, String[] permissions, int requestCode){
ActivityCompat.requestPermissions(activity, permissions, requestCode);
}
}
模拟 class(例如,测试并确保您的 activity 正确处理 onRequestPermissionsResult
)
public class PermissionsHandlerMocked implements PermissionsHandler {
@Override
public boolean checkHasPermission(AppCompatActivity activity, String permission) {
return false;
}
@Override
public void requestPermission(AppCompatActivity activity, String[] permissions, int requestCode){
int[] grantResults = new int[permissions.length];
for (int i = 0; i < permissions.length; i++) {
grantResults[i] = PackageManager.PERMISSION_GRANTED
}
activity.onRequestPermissionResult(requestCode, permissions, grantResults);
}
}
然后在你的 activity:
PermissionsHandler permissionsHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
permissionsHandler = Injection.providePermissionsHandler();
//or however you choose to inject your production vs mocked handler.
}
//method from your view interface, to be called by your presenter
@Override
void requestLocationPermission() {
permissionsHandler.requestPermision((AppCompatActivity) this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_CODE_LOCATION};
}
fobo66,你总是可以让视图实现更通用的方法,比如
checkLocationPermissionGranted()
和 requestLocationPermission()
。然后您的视图实现可以根据需要引用 activity,而您的演示者永远不必触及 activity 引用。
权限请求和状态是视图(片段或 Activity)的责任,因为取决于用户发出请求或授予权限的操作。我使用 MVP 管理权限如下(读取外部存储示例):
我的合同
interface View {
...
void requestReadPermission();
boolean areReadPermissionGranted();
void showPermissionError();
void hidePermissionError();
...
}
interface Presenter {
...
void setReadPermissions(boolean grantedPermissions);
...
}
interface Model {
...
}
我的观点实现。 (在这种情况下是片段,但它可以是 Activity 或其他任何内容,Presenter 将只期望响应)。
public class MyView extends Fragment implements Contract.View {
...
Contract.Presenter presenter;
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
boolean grantedPermissions = (grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED);
presenter.setReadPermissions(grantedPermissions);
}
@Override
public void showPermissionError() {
// Show not permission message
}
@Override
public void hidePermissionError() {
// Hide not permission message
}
@Override
public void requestReadPermission() {
this.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}
@Override
public boolean areReadPermissionGranted() {
return ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}
...
以及 Presenter 实现
public class MyPresenter implements Contract.Presenter {
...
Contract.View view;
public void doSomethingThatRequiresPermissions() {
...
if ( !view.areReadPermissionGranted() ) {
view.requestReadPermission();
view.showPermissionError();
} else {
view.hidePermissionError();
doSomethingWithPermissionsGranted();
}
...
}
@Override
public void setReadPermissions(boolean grantedPermissions) {
if( grantedPermissions ){
view.hidePermissionError();
doSomethingThatRequiresPermissions();
} else {
view.showPermissionError();
}
}
public void doSomethingWithPermissionsGranted(){
...
}
然后你可以像
那样做单元测试Contract.View mockedView;
@Test
public void requestAlbumListWithoutPermissions() {
when(mockedView.areReadPermissionGranted()).thenReturn(false);
presenter.doSomethingWithPermissionsGranted();
verify(mockedView).showPermissionError();
verify(mockedView).requestReadPermission();
}