Git Product home page Git Product logo

dexter's Introduction

Karumi logo Dexter Build Status Maven Central Android Arsenal

This project is no longer under active development. If you are looking for an Android library to be able to request Android permissions in runtime take a look here

Dexter is an Android library that simplifies the process of requesting permissions at runtime.

Android Marshmallow includes a new functionality to let users grant or deny permissions when running an app instead of granting them all when installing it. This approach gives the user more control over applications but requires developers to add lots of code to support it.

Dexter frees your permission code from your activities and lets you write that logic anywhere you want.

Screenshots

Demo screenshot

Usage

Dependency

Include the library in your build.gradle

dependencies{
    implementation 'com.karumi:dexter:6.2.3'
}

To start using the library you just need to call Dexter with a valid Context:

public MyActivity extends Activity {
	@Override public void onCreate() {
		super.onCreate();
		Dexter.withContext(activity)
			.withPermission(permission)
			.withListener(listener)
			.check();
	}
}

Single permission

For each permission, register a PermissionListener implementation to receive the state of the request:

Dexter.withContext(this)
	.withPermission(Manifest.permission.CAMERA)
	.withListener(new PermissionListener() {
		@Override public void onPermissionGranted(PermissionGrantedResponse response) {/* ... */}
		@Override public void onPermissionDenied(PermissionDeniedResponse response) {/* ... */}
		@Override public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {/* ... */}
	}).check();

To make your life easier we offer some PermissionListener implementations to perform recurrent actions:

  • BasePermissionListener to make it easier to implement only the methods you want. Keep in mind that you should not call super methods when overriding them.
  • DialogOnDeniedPermissionListener to show a configurable dialog whenever the user rejects a permission request:
PermissionListener dialogPermissionListener =
	DialogOnDeniedPermissionListener.Builder
		.withContext(context)
		.withTitle("Camera permission")
		.withMessage("Camera permission is needed to take pictures of your cat")
		.withButtonText(android.R.string.ok)
		.withIcon(R.mipmap.my_icon)
		.build();
  • SnackbarOnDeniedPermissionListener to show a snackbar message whenever the user rejects a permission request:
PermissionListener snackbarPermissionListener =
	SnackbarOnDeniedPermissionListener.Builder
		.with(view, "Camera access is needed to take pictures of your dog")
		.withOpenSettingsButton("Settings")
        .withCallback(new Snackbar.Callback() {
            @Override
            public void onShown(Snackbar snackbar) {
                // Event handler for when the given Snackbar is visible
            }
            @Override
            public void onDismissed(Snackbar snackbar, int event) {
                // Event handler for when the given Snackbar has been dismissed
            }
        }).build();
  • CompositePermissionListener to compound multiple listeners into one:
PermissionListener snackbarPermissionListener = /*...*/;
PermissionListener dialogPermissionListener = /*...*/;
PermissionListener compositePermissionListener = new CompositePermissionListener(snackbarPermissionListener, dialogPermissionListener, /*...*/);

Multiple permissions

If you want to request multiple permissions you just need to call withPermissions and register an implementation of MultiplePermissionsListener:

Dexter.withContext(this)
	.withPermissions(
		Manifest.permission.CAMERA,
		Manifest.permission.READ_CONTACTS,
		Manifest.permission.RECORD_AUDIO
	).withListener(new MultiplePermissionsListener() {
	    @Override public void onPermissionsChecked(MultiplePermissionsReport report) {/* ... */}
	    @Override public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {/* ... */}
	}).check();

The MultiplePermissionsReport contains all the details of the permission request like the list of denied/granted permissions or utility methods like areAllPermissionsGranted and isAnyPermissionPermanentlyDenied.

As with the single permission listener, there are also some useful implementations for recurring patterns:

  • BaseMultiplePermissionsListener to make it easier to implement only the methods you want. Keep in mind that you should not call super methods when overriding them.
  • DialogOnAnyDeniedMultiplePermissionsListener to show a configurable dialog whenever the user rejects at least one permission:
MultiplePermissionsListener dialogMultiplePermissionsListener =
	DialogOnAnyDeniedMultiplePermissionsListener.Builder
		.withContext(context)
		.withTitle("Camera & audio permission")
		.withMessage("Both camera and audio permission are needed to take pictures of your cat")
		.withButtonText(android.R.string.ok)
		.withIcon(R.mipmap.my_icon)
		.build();
  • SnackbarOnAnyDeniedMultiplePermissionsListener to show a snackbar message whenever the user rejects any of the requested permissions:
MultiplePermissionsListener snackbarMultiplePermissionsListener =
	SnackbarOnAnyDeniedMultiplePermissionsListener.Builder
		.with(view, "Camera and audio access is needed to take pictures of your dog")
		.withOpenSettingsButton("Settings")
        .withCallback(new Snackbar.Callback() {
            @Override
            public void onShown(Snackbar snackbar) {
                // Event handler for when the given Snackbar is visible
            }
            @Override
            public void onDismissed(Snackbar snackbar, int event) {
                // Event handler for when the given Snackbar has been dismissed
            }
        })
		.build();
  • CompositePermissionListener to compound multiple listeners into one:
MultiplePermissionsListener snackbarMultiplePermissionsListener = /*...*/;
MultiplePermissionsListener dialogMultiplePermissionsListener = /*...*/;
MultiplePermissionsListener compositePermissionsListener = new CompositeMultiplePermissionsListener(snackbarMultiplePermissionsListener, dialogMultiplePermissionsListener, /*...*/);

Handling listener threads

If you want to receive permission listener callbacks on the same thread that fired the permission request, you just need to call onSameThread before checking for permissions:

Dexter.withContext(context)
	.withPermission(permission)
	.withListener(listener)
	.onSameThread()
	.check();

Showing a rationale

Android will notify you when you are requesting a permission that needs an additional explanation for its usage, either because it is considered dangerous, or because the user has already declined that permission once.

Dexter will call the method onPermissionRationaleShouldBeShown implemented in your listener with a PermissionToken. It's important to keep in mind that the request process will pause until the token is used, therefore, you won't be able to call Dexter again or request any other permissions if the token has not been used.

The most simple implementation of your onPermissionRationaleShouldBeShown method could be:

@Override public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
	token.continuePermissionRequest();
}

Error handling

If you think there is an error in your Dexter integration, just register a PermissionRequestErrorListener when calling Dexter:

Dexter.withContext(context)
	.withPermission(permission)
	.withListener(listener)
	.withErrorListener(new PermissionRequestErrorListener() {
		@Override public void onError(DexterError error) {
			Log.e("Dexter", "There was an error: " + error.toString());
		}
	}).check();

The library will notify you when something bad happens. In general, it is a good practice to, at least, log every error Dexter may throw but is up to you, the developer, to do that.

IMPORTANT: Remember to follow the Google design guidelines to make your application as user-friendly as possible.

Permission dialog not being shown

If you are using the MultiplePermissionsListener and you don't see the permission dialog the second time the permission is checked review your configuration. Keep in mind you need to let Dexter know the rationale you can show was closed or not by calling token?.continuePermissionRequest(). If you don't do this, the next time the permission is requested, the OS dialog asking for this permission won't be shown. You can find more information about this in here. This is an example of how a multiple permission request should be implemented:

button.setOnClickListener {
    Dexter.withContext(this@MainActivity)
                        .withPermissions(
                            Manifest.permission.ACCESS_COARSE_LOCATION
                            ,Manifest.permission.ACCESS_FINE_LOCATION)
                        .withListener(object: MultiplePermissionsListener {
                            override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
                                report?.let {
                                    if(report.areAllPermissionsGranted()){
                                        toast("OK")
                                    }
                                }
                            }
                            override fun onPermissionRationaleShouldBeShown(
                                permissions: MutableList<PermissionRequest>?,
                                token: PermissionToken?
                            ) {
                                // Remember to invoke this method when the custom rationale is closed
                                // or just by default if you don't want to use any custom rationale.
                                token?.continuePermissionRequest()
                            }
                        })
                        .withErrorListener {
                            toast(it.name)
                        }
                        .check()
}

Caveats

  • For permissions that did not exist before API Level 16, you should check the OS version and use ContextCompat.checkSelfPermission. See You Cannot Hold Non-Existent Permissions.
  • Don't call Dexter from an Activity with the flag noHistory enabled. When a permission is requested, Dexter creates its own Activity internally and pushes it into the stack causing the original Activity to be dismissed.
  • Permissions SYSTEM_ALERT_WINDOW and WRITE_SETTINGS are considered special by Android. Dexter doesn't handle those and you'll need to request them in the old fashioned way.

Contributors

Thank you all for your work!


Carlos Morera de la Chica

Alex Florescu

Pedro Veloso

Dion Segijn

Christian Panadero

Vignesh

Andy French

Bernat Borrás Paronella

Bastien Paul

Bas Broek

Bartek Lipinski

emmano

Konrad Morawski

Hrant Alaverdyan

Carla

Pavel Stepanov

Emmett Wilson

Max

Al B.

Vladislav Bauer

Jc Miñarro

handrenliang

jcruzsousa

lzdon

Do you want to contribute?

Feel free to add any useful feature to the library, we will be glad to improve it with your help.

Keep in mind that your PRs must be validated by Travis-CI. Please, run a local build with ./gradlew checkstyle build test before submiting your code.

Libraries used in this project

License

Copyright 2015 Karumi

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.

dexter's People

Contributors

albodelu avatar alorma avatar andyfrench avatar anothem avatar basthomas avatar bastienpaulfr avatar binarynoise avatar carlosmchica avatar danielmartinus avatar davideme avatar deinlandel avatar edluke avatar eduardbosch avatar emmano avatar emmettwilson avatar esteveaguilera avatar flipper83 avatar hamorillo avatar handrenliang avatar jcminarro avatar jcruzsousa avatar komitake avatar panavtec avatar pedronveloso avatar pedrovgs avatar rolf-smit avatar serchinastico avatar themaxcoder avatar untalfranfernandez avatar vbauer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dexter's Issues

Crash when request permission

I have got crash error when I trying to request permission. Which below:

Caused by: java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:requestPermissions:, request=42, result=-1, data=Intent { act=android.content.pm.action.REQUEST_PERMISSIONS (has extras) }} to activity {com.feelsfashionltd.feels.debug/com.karumi.dexter.DexterActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Context android.view.ViewGroup.getContext()' on a null object reference at android.app.ActivityThread.deliverResults(ActivityThread.java:3988) at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3301) at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3359)  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2549)  at android.app.ActivityThread.access$900(ActivityThread.java:150)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1394)  at android.os.Handler.dispatchMessage(Handler.java:102)  at android.os.Looper.loop(Looper.java:168)  at android.app.ActivityThread.main(ActivityThread.java:5845)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)  Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Context android.view.ViewGroup.getContext()' on a null object reference at android.support.design.widget.Snackbar.<init>(Snackbar.java:180) at android.support.design.widget.Snackbar.make(Snackbar.java:209) at com.karumi.dexter.listener.multi.SnackbarOnAnyDeniedMultiplePermissionsListener.showSnackbar(SnackbarOnAnyDeniedMultiplePermissionsListener.java:63) at com.karumi.dexter.listener.multi.SnackbarOnAnyDeniedMultiplePermissionsListener.onPermissionsChecked(SnackbarOnAnyDeniedMultiplePermissionsListener.java:58) at com.karumi.dexter.listener.multi.CompositeMultiplePermissionsListener.onPermissionsChecked(CompositeMultiplePermissionsListener.java:54) at com.karumi.dexter.MultiplePermissionListenerThreadDecorator$1.run(MultiplePermissionListenerThreadDecorator.java:45) at com.karumi.dexter.MainThread.execute(MainThread.java:32) at com.karumi.dexter.MultiplePermissionListenerThreadDecorator.onPermissionsChecked(MultiplePermissionListenerThreadDecorator.java:43) at com.karumi.dexter.DexterInstance.onPermissionsChecked(DexterInstance.java:264) at com.karumi.dexter.DexterInstance.updatePermissionsAsDenied(DexterInstance.java:247) at com.karumi.dexter.DexterInstance.onPermissionRequestDenied(DexterInstance.java:148) at com.karumi.dexter.Dexter.onPermissionsRequested(Dexter.java:176) at com.karumi.dexter.DexterActivity.onRequestPermissionsResult(DexterActivity.java:54) at android.app.Activity.dispatchRequestPermissionsResult(Activity.java:6564) at android.app.Activity.dispatchActivityResult(Activity.java:6443) at android.app.ActivityThread.deliverResults(ActivityThread.java:3984) at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3301)  at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3359)  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2549)  at android.app.ActivityThread.access$900(ActivityThread.java:150)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1394)  at android.os.Handler.dispatchMessage(Handler.java:102)  at android.os.Looper.loop(Looper.java:168)  at android.app.ActivityThread.main(ActivityThread.java:5845)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687) 

Can you check this error and improve it?

Steps to reproduce

First I request Storage permission -> choose allow
Then I request Camera, Record Audio and Storage permission -> Deny all -> Crash

Version of the library

2.2.2

does not deal with saveinstancestate

I see your using static references to instance, this means if your activity is killed during the request (can happen with rotate or if the user switches out of the app to google a permission) this will not work.

Keep execution context on permission requested.

Hi guys,

I just found out a behaviour that I was not expecting, although I'm not quite sure it should be consider as an issue. But definitely it's something I wasn't expecting.

If a permission is requested from a background thread the result is returned on the main thread.

I was having a look at the code and the reason seems to be that when the internal activity that the lib uses is started it changes the thread under the covers.

I'd like to know what are you thoughts about this.

Cheers.

Checking Permissions results in activity finish/start loop

I have 2 activities in my project

Activity#1 is a kind of a StartupActivity, which handles Registration/Login and a "Splashscreen" in which some REST Calls have to be made.
If everything is fine within the SplashScreen the MainActivity will be started and Activity#1 will be finished.

Even if the MainActivity (singleTop) is resumed, the StartupActivity is shown upfront.

In the MainActivity I call Dexter.checkPermission() after onCreate etc. before I check for permissions I call isRequestOngoing() which returns false...

Expected behaviour

  • Check for permissions

Actual behaviour

  • The MainActivity finishes, Activity#1 comes back to front, which starts MainActivity, which finishes again... Its a fun loop.

Steps to reproduce

  • Have a StartupActivity which will come up front, if MainActivity is resumed

Version of the library

2.2.2

Group callbacks

What for two callbacks?

    @Override public void onPermissionGranted(PermissionGrantedResponse response) {/* ... */}
    @Override public void onPermissionDenied(PermissionDeniedResponse response) {/* ... */}

May be the one with boolean will be less code?

    @Override public void onPermissionResult(PermissionGrantedResponse response) {
         if(response.isGranted()){...} else {...}
}

?

Package installer has stopped.

This error is happenning on sample application in genymotion vm (preview of google nexus 5x, android 6) :

Dexter.checkPermission(Manifest.permission.INTERNET, new EmptyPermissionListener() {

immediatelly FC the package manager, the logcat :

11-25 12:11:01.575 3708-3708/? E/AndroidRuntime: FATAL EXCEPTION: main
                                                 Process: com.android.packageinstaller, PID: 3708
                                                 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.android.packageinstaller/com.android.packageinstaller.permission.ui.GrantPermissionsActivity}: java.lang.NullPointerException: Attempt to get length of null array
                                                     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
                                                     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
                                                     at android.app.ActivityThread.-wrap11(ActivityThread.java)
                                                     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
                                                     at android.os.Handler.dispatchMessage(Handler.java:102)
                                                     at android.os.Looper.loop(Looper.java:148)
                                                     at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                     at java.lang.reflect.Method.invoke(Native Method)
                                                     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
                                                  Caused by: java.lang.NullPointerException: Attempt to get length of null array
                                                     at com.android.packageinstaller.permission.ui.GrantPermissionsActivity.computePermissionGrantState(GrantPermissionsActivity.java:277)
                                                     at com.android.packageinstaller.permission.ui.GrantPermissionsActivity.updateDefaultResults(GrantPermissionsActivity.java:327)
                                                     at com.android.packageinstaller.permission.ui.GrantPermissionsActivity.onCreate(GrantPermissionsActivity.java:100)
                                                     at android.app.Activity.performCreate(Activity.java:6237)
                                                     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
                                                     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
                                                     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476at android.app.ActivityThread.-wrap11(ActivityThread.javaat android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344at android.os.Handler.dispatchMessage(Handler.java:102at android.os.Looper.loop(Looper.java:148at android.app.ActivityThread.main(ActivityThread.java:5417at java.lang.reflect.Method.invoke(Native Methodat com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:61611-25 12:11:01.576 848-946/system_process W/ActivityManager:   Force finishing activity com.android.packageinstaller/.permission.ui.GrantPermissionsActivity

Dexter only called once

Expected behaviour

Dexter to ask again for permissions if they are turned off in the app settings after having accepted them

Actual behaviour

When accepting permissions and afterwards turning them off in the settings; I'm expecting Dexter to be called in the onResume method in my BaseActivity. Since there is already a running instance it is called only once.

Steps to reproduce

Accept permissions, go to settings, go back to the app and see that the permissions are not requested in the onResume

@Override
    protected void onResume() {
        super.onResume();

        if (!Dexter.isRequestOngoing()) {
            Dexter.checkPermissions(new MultiplePermissionsListener() {
                @Override public void onPermissionsChecked(MultiplePermissionsReport report) {/* ... */}
                @Override public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {/* ... */}
            }, android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.CAMERA);
        }
     }

Version of the library

com.karumi:dexter:2.2.2

Crash when initializing Dexter 3.0.0 with a non-activity context

The transparent activity is no longer started with FLAG_ACTIVITY_NEW_TASK, which results in the following crash:

android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
    at android.app.ContextImpl.startActivity(ContextImpl.java:672)
    at android.app.ContextImpl.startActivity(ContextImpl.java:659)
    at android.content.ContextWrapper.startActivity(ContextWrapper.java:331)
    at com.karumi.dexter.DexterInstance.startTransparentActivityIfNeeded(DexterInstance.java:216)
    at com.karumi.dexter.DexterInstance.checkMultiplePermissions(DexterInstance.java:322)
    at com.karumi.dexter.DexterInstance.checkSinglePermission(DexterInstance.java:294)
    at com.karumi.dexter.DexterInstance.checkPermission(DexterInstance.java:80)
    at com.karumi.dexter.Dexter.checkPermission(Dexter.java:90)

I know 3.0.0 isn't released yet; just wanted to bring it to your attention in case it wasn't known.

Demo crashes when rotating.

  1. Request a permission.
  2. While the rationale is showing, rotate the device (the dialog will be dismissed).
  3. Click the button request the permission again.
  4. App crashes.
1-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime: FATAL EXCEPTION: main
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime: Process: com.karumi.dexter.sample, PID: 20300
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime: java.lang.IllegalStateException: Only one permission request at a time. Currently handling permission: [android.permission.READ_CONTACTS]
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at com.karumi.dexter.DexterInstance.checkPermission(DexterInstance.java:51)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at com.karumi.dexter.Dexter.checkPermission(Dexter.java:53)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at com.karumi.dexter.sample.SampleActivity.onContactsPermissionButtonClicked(SampleActivity.java:70)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at com.karumi.dexter.sample.SampleActivity$$ViewBinder$2.doClick(SampleActivity$$ViewBinder.java:34)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at butterknife.internal.DebouncingOnClickListener.onClick(DebouncingOnClickListener.java:22)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at android.view.View.performClick(View.java:5198)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at android.view.View$PerformClick.run(View.java:21147)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at android.os.Handler.handleCallback(Handler.java:739)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:95)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:148)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:5417)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
11-23 17:17:32.121 20300-20300/com.karumi.dexter.sample E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

Dexter activity seriously limits application capabilities

Greetings Dexter team,

This issue could be related to issue #45

I have an Activity (Webview) which I call from browser with View intent and request for permissions through dexter and when I do this opens up the main activity since the DexterActivity class is called as NEW TASK. This would be against my application flow and I'm sure that this would affect many.

I'm requesting MultiplePermissions.

Dexter is an extremely useful framework and I have integrated it successfully through out my app but due to this issue I have to rework the flow.

Translucent animation flickering when checking for already granted permission

Expected behaviour

If all requested permissions are already granted when invoking one of the Dexter.checkPermission(s)(OnSameThread), startTransparentActivityIfNeeded() should not start DexterActivity. Thus preventing any kind of flicker and animation when opening/closing activities.

Actual behaviour

When checking for permissions already granted, I get a slight flicker and slide animation with a translucent activity with a few shadows.

Steps to reproduce

I have no clue... I have two classes, implemented with the same exact pattern, one of them shows the flicker, the other doesn't. They check for different permissions but I tried to replicate everything in the working class into the non-working class but I can't get it to work.

For some strange reason, I have a piece of code fully working (no flicker, no animation) and another piece of code not working (shows flicker, shows animation). I can't really understand the differences between them to describe the steps to reproduce.

That's why I'm suggesting to not even start DexterActivity if all required permissions are already granted. I know this is supposed to be a translucent activity, but why are we opening it if we are going to close it right away since there are no dialogs to show?

Version of the library

v2.3.0

Permission rational not showing on the first permission request

Expected behaviour

onPermissionRationaleShouldBeShown(List permissions, PermissionToken token) fires always when Dexter.checkPermissions is called

Actual behaviour

Cannot understand why this happens but when I firstly call Dexter.checkPermissions with two permissions: Manifest.permission.ACCESS_FINE_LOCATION and Manifest.permission.ACCESS_COARSE_LOCATION onPermissionRationaleShouldBeShown is not called but instead dialog with permission request shows. What is more there is no "Never ask again" checkbox:
screen1

When I deny and call method second time, onPermissionRationaleShouldBeShown is called and I show my alertDialog and then permission request dialog has "Never ask again" checkbox.
screen2

Steps to reproduce

Dexter.checkPermissions(new CustomPermissionListener(), Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION);

Nexus 5, 6.0.1

Version of the library

com.karumi:dexter:2.2.2

Make the Dexter API fluent

I opened an issue talking about adding a new parameter to notify Dexter errors (#53) and seems to me that adding new parameters to the current API will eventually make it impossible to develop.

I think it would be much better to create a fluent API to use Dexter both in readability and extensibility. The proposed API would be used like:

public void someMethod() {
    Dexter.withPermissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS)
          .withListener(someListener)
          .withErrorListener(errorListener)
          .onSameThread()
          .check();
}

Thoughts are more than welcome.

No way to synchronize Dexter requests, sample app crashes

Hi there,
the library looks promising, thanks. However it's very easy to crash your sample app, all it takes is tapping on a couple of buttons rapidly (before the dialog box pops up):

FATAL EXCEPTION: main
Process: com.karumi.dexter.sample, PID: 2514
java.lang.IllegalStateException: Only one Dexter request at a time is allowed
    at com.karumi.dexter.DexterInstance.checkNoDexterRequestOngoing(DexterInstance.java:215)
    at com.karumi.dexter.DexterInstance.checkPermissions(DexterInstance.java:80)
    at com.karumi.dexter.DexterInstance.checkPermission(DexterInstance.java:69)
    at com.karumi.dexter.Dexter.checkPermission(Dexter.java:57)
    at com.karumi.dexter.sample.SampleActivity.onContactsPermissionButtonClicked(SampleActivity.java:74)
    at com.karumi.dexter.sample.SampleActivity$$ViewBinder$3.doClick(SampleActivity$$ViewBinder.java:43)
    at butterknife.internal.DebouncingOnClickListener.onClick(DebouncingOnClickListener.java:22)
    at android.view.View.performClick(View.java:5198)
    at android.view.View$PerformClick.run(View.java:21147)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:148)
    at android.app.ActivityThread.main(ActivityThread.java:5417)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

How could it be prevented? isRequestingPermission is private and invisible from outside, so the only way would be to create an own wrapper around Dexter and track all permission requests ourselves, but this feels cumbersome. Any ideas?

Calling Dexter.checkPermissions closes FragmentActivity

I am using Dexter.checkPermissions to ask for Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE.

Expected behaviour

After user grant access or deny access, it should execute nothing.

Actual behaviour

It closes the FragmentActivity.

Steps to reproduce

PermissionUtil

 public static void checkMicrophoneWriteStoragePermissionVoiceMessage(final Context context, final PermissionListener listener) {
        Dexter.checkPermissionsOnSameThread(new MultiplePermissionsListener() {
            @Override
            public void onPermissionsChecked(MultiplePermissionsReport report) {
                if (report.isAnyPermissionPermanentlyDenied()) {
                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
                    builder.setTitle(R.string.app_name)
                            .setMessage(R.string.permission_microphone_voice_message_never_ask_again)
                            .setNeutralButton(R.string.ok, null)
                            .create()
                            .show();
                    listener.onPermissionDenied();
                    return;
                }

                if (report.areAllPermissionsGranted()) {
                    listener.onPermissionGranted();
                } else {
                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
                    builder.setTitle(R.string.app_name)
                            .setMessage(R.string.permission_microphone_voice_message_denied)
                            .setNeutralButton(R.string.ok, null)
                            .create()
                            .show();

                    listener.onPermissionDenied();
                }
            }

            @Override
            public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, final PermissionToken token) {
                new AlertDialog.Builder(context)
                        .setMessage(R.string.permission_microphone_voice_message_rationale)
                        .setPositiveButton(R.string.button_allow, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                token.continuePermissionRequest();
                            }
                        })
                        .setNegativeButton(R.string.button_deny, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                token.cancelPermissionRequest();
                            }
                        })
                        .show();
            }
        }, Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE);
    }

public interface PermissionListener {
        void onPermissionGranted();
        void onPermissionDenied();
    }

In Fragment:

private void getVoiceMessagingPermission() {
PermissionUtil.checkMicrophoneWriteStoragePermissionVoiceMessage(mContext, toggleUiForVoiceMessagingPermissionListener);
}

private PermissionUtil.PermissionListener toggleUiForVoiceMessagingPermissionListener = new PermissionUtil.PermissionListener() {
        @Override
        public void onPermissionGranted() {
            // do nothing
            Toast.makeText(mContext, "Voice Messaging Permission Granted.", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPermissionDenied() {
            // do nothing
            Toast.makeText(mContext, "Voice Messaging Permission Denied.", Toast.LENGTH_SHORT).show();
        }
    };

Version of the library

compile 'com.karumi:dexter:2.2.2'

Can not find withCallback

I can't find SnackbarOnDeniedPermissionListener.Builder.withCallback API

Version of the library

com.karumi:dexter:2.2.2

Black screen with Launch Activity without Toolbar.

Hi, I'm using Dexter-Lib on a project and I am having a problem that always displays a black screen. The details to simulate are simple:

1 - build.app configurations.

android {
    compileSdkVersion 23
    buildToolsVersion '23.0.2'

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        multiDexEnabled true
    }
}

2 - My style.

<style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorAccent">@color/material_white_1000</item>
    <item name="colorControlNormal">@color/material_white_1000</item>
    <item name="colorControlActivated">@color/material_white_1000</item>
    <item name="colorControlHighlight">@color/material_white_1000</item>
    <item name="windowNoTitle">true</item>
    <item name="windowActionBar">false</item>
</style>

3 - I create a simple builder to call the Dexter Library.

PermissionValidator.with(activity)
        .setTitle(R.string.permission_read_contacts_title)
        .setMessage(R.string.permission_read_contacts_message)
        .setDeniedMessage(R.string.permission_read_contacts_denied_message)
        .setPermission(Manifest.permission.READ_CONTACTS)
        .setOnPermissionGrantedListener(new OnPermissionGrantedListener() {
            @Override
            public void onPermissionGranted() {
                activity.startActivityForResult(getContactsIntent(), PICK_CONTACT_REQUEST_CODE);
            }
        })
        // .setOnPermissionRationaleShouldBeShownListener(new DefaultOnPermissionRationaleShouldBeShownListener())
        .validate();

4 - The code called by Validate (if necessary).

if (!Dexter.isRequestOngoing()) {
    Dexter.checkPermission(new PermissionListener() {
        @Override
        public void onPermissionGranted(PermissionGrantedResponse response) {
            if (onPermissionGrantedListener != null) {
                onPermissionGrantedListener.onPermissionGranted();
            }
        }

        @Override
        public void onPermissionDenied(PermissionDeniedResponse response) {
            if (onPermissionDeniedListener != null) {
                onPermissionDeniedListener.onPermissionDenied();
            }
        }

        @Override
        public void onPermissionRationaleShouldBeShown(PermissionRequest permission, final PermissionToken token) {
            if (onPermissionRationaleShouldBeShownListener != null) {
                onPermissionRationaleShouldBeShownListener.onPermissionRationaleShouldBeShown(context, token, title, message, deniedMessage);
            }
        }
    }, permission);
}

This code works perfectly when you define a Toolbar. But when I do this in my LoginActivity (which does not use a Toolbar), I get one of two messages when I call the .validate () method and a beautiful black screen:

1 - You need to use a Theme.AppCompat theme (or descendant) with this activity`.
2 - android.view.WindowLeaked: Activity br.com.mobicare.recarga.ui.activity.LaunchActivity has leaked window com.android.internal.policy.PhoneWindow$DecorView{707ca14 V.E...... R....... 0,0-1080,737} that was originally added here`

Result

Could anyone help me?

Keyboard does not open when checking permissions < android M

Probably because of the transparent activity.

A possible fix is:

getWindow().addFlags(
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);

Version of the library

2.3.0

Show the permission dialog after a defined delay

Expected behaviour

Add a way to show the Dialog after a defined delay

Actual behaviour

The permission delay appears just when we call it. If we want to show it after some time, we've to write it in our apps

Steps to reproduce

Call Dexter.checkPermission()

Version of the library

2.2.2

If you want, I can do it and launch a PR. Are you interested on it? If you are not interested in this feature, don't hesitate to simply close this issue :·)

Thanks for your work!

Saving state of some permission

This is probably not a lib issue, but the one of not understanding one and I'm sure some other devs have the same question.

I ask for permission with PermissionListener and override those three methods. What happens in permission denied is up to me. But when I (for example, want to fetch location of the user) and he/she didn't gave the app permission to access FINE_LOCATION, it's up to me to save that permission denied somewhere (SharedPreferences?) and then in that code for fetching, I have to check everytime the state of that permission that I previously saved in (SharedPreferences?)?

My question is then, has the library some in-built mechanism to save if the permission is denied and then with that I can check that some code shouldn't be executed? For the case when permission is given to the app, everything is working fine out of the box.

btw. great lib, helps a lot :) 👍

Create a silent version to check permissions that doesn't crash if already doing it

There seems to be a problem with the current usage of the library, people is often surprised with the Only one Dexter request at a time is allowed exception and are forced to write a snippet similar to:

if (!Dexter.isRequestOngoing()) {
      Dexter.checkPermission(someListener, Manifest.permission.RECORD_AUDIO);
}

We can simplify the library usage by removing the exception and notifying an optional callback if there is a problem instead of forcing everyone to handle that scenario. Adding a new parameter to every Dexter method to pass the optional callback seems to be a non-scalable option but the only one compatible with the current implementation.

Thoughts are welcome!

Follow git-flow for the repo

Hi guys!

Would it be possible for you to follow git-flow for the repo?
It can be a bit confusing, if you've got not-yet-released features on your master branch.
E.g. I wanted to peek into your sample module, and saw completely different PermissionListener being used. And I needed to perform a small investigation to see if I was doing something wrong or if it was just something not yet released.

Thanks

Not checking permission request API under 23 (M)

I released my app using dexter and I found that there is some bugs with Android system and I cause Only one dexter request is allowed at a time exception.
I'm hoping not to check permission stuff under API version 23.

Here is my codes and error message.

Dexter.checkPermissions(new MultiplePermissionsListener() {
                @Override
                public void onPermissionsChecked(MultiplePermissionsReport report) {
                    if (report.areAllPermissionsGranted())
                        showCropSheetView();
                    else
                        Snackbars.alert(CropActivity.this, getString(R.string.errorMessageExternalStoragePermission));
                }

                @Override
                public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
                    Snackbars.alert(CropActivity.this, getString(R.string.errorMessageExternalStoragePermission));
                    token.continuePermissionRequest();
                }
            }, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE);

untitled

Provide example on how to handle device rotation.

When you request a permission, Dexter makes note that a permission request is outstanding. If I previously denied a permission, I'll get the callback showPermissionRationale(PermissionToken). We're supposed to use this to continue our permission request with PermissionToken.continuePermissionRequest() or cancel it with PermissionToken.cancelPermissionRequest(). However, if I choose to show an AlertDialog (as is done in the SampleActivity, the user could rotate the device, leading to me losing access to my token and being unable to cancel or continue the request.

This would lead to the crasher described in #14, which was fixed by locking the orientation of the sample app.

What I'd like to see is an example on how you suggest we recover our state after a device rotation. Specifically, what is your suggested way to persist the PermissionToken?

It'd be great if this were in the sample app.

Dialog not showing up after firts "Deny"

Expected behaviour

Show a default Dialog when requesting a permission after the first "Deny"

Actual behaviour

Nothing appears

Steps to reproduce

  1. Request permission
  2. Deny permission
  3. Request again permission

Version of the library

2.2.2

Code

It only happens when I use the default dialogPermissionListener:

DialogOnDeniedPermissionListener.Builder
    .withContext(context)
    .withTitle("Camera permission")
    .withMessage("Camera permission is needed to take pictures of your cat")
    .withButtonText(android.R.string.ok)
    .withIcon(R.mipmap.my_icon)
    .build();

Using this sample code:

PermissionListener snackbarPermissionListener =
                SnackbarOnDeniedPermissionListener.Builder
                        .with(rootView, "Camera access is needed to take pictures of your dog")
                        .withOpenSettingsButton("Settings")
                        .build();
        Dexter.checkPermission(
                new CompositePermissionListener(snackbarPermissionListener, dialogPermissionListener),
                Manifest.permission.CAMERA
        );

Create in-depth acceptance tests

As the library is getting bigger we need acceptance tests to verify all the cases we are handling.
Playing around with permissions in UI tests is usually a bad idea but this project is probably the best to do it anyways.
Do some research to run one test at a time (probably with Spoon) and reset device permissions before the execution of each test. It will be slow as hell but probably better than nothing.

it will call onResume?

when I call checkPermission,it will call onResume

Steps to reproduce

Dexter.checkPermissions

Version of the library

2.3.0

DexterActivity is still leak even set launchMode="singleTask"

Snapshot:
e40bde2d0fb41a0fd4f5e3276a3813a4
Proguard mapping info:

com.karumi.dexter.Dexter -> com.karumi.dexter.Dexter:  
    com.karumi.dexter.DexterInstance instance -> a  
com.karumi.dexter.DexterInstance -> im:  
    android.app.Activity activity -> i

PR #25 have tried to fix this issue. Please review and test it.
Thanks a lot!

Permission Dialog not showing at first launch

Expected behavior

Application should ask camera permission at first time camera call from my app.

Actual behaviour

After installing application in device when i click camera button it is not showing camera permission dialog instead it is directly opening camera. but after clearing app data in application setting. it started showing permission request dialog. Now started work fine. even i am targating api 23 and my device also using marshmallow. if change the package name of app then it work normal as i want

Steps to reproduce

Version of the library

I am using latest version

Requesting some permissions doesn't work

When you request for a Manifest.permission.ACCESS_COARSE_LOCATION or Manifest.permission.ACCESS_FINE_LOCATION Dexter doesn't work. It is because the hashcode of these permissions is a negative value and you can't use a nevative value as a requestCode.

Dexter.checkPermissionOnSameThread crashes when Looper has already been prepared

Expected behaviour

Calling Dexter.checkPermissionOnSameThread shouldn't crash if a looper has already been prepared

Actual behaviour

03-14 17:00:16.549 16003-16614/com.blah E/AndroidRuntime: FATAL EXCEPTION: IntentService[GeofenceTransitionsIntentService]
                                                                       Process: com.blah, PID: 16003
                                                                       java.lang.RuntimeException: Only one Looper may be created per thread
                                                                           at android.os.Looper.prepare(Looper.java:82)
                                                                           at android.os.Looper.prepare(Looper.java:77)
                                                                           at com.karumi.dexter.WorkerThread.<init>(WorkerThread.java:30)
                                                                           at com.karumi.dexter.ThreadFactory.makeSameThread(ThreadFactory.java:40)
                                                                           at com.karumi.dexter.Dexter.checkPermissionOnSameThread(Dexter.java:60)
                                                                           at ...

Steps to reproduce

In a background thread, call Looper.prepare() before calling Dexter.checkPermissionOnSameThread

Version of the library

2.2.1

Fix

According to stack overflow adding the following to WorkerThread.java should fix it (or at least this exception):

if (Looper.myLooper() == null) {
    Looper.prepare();
}

I haven't tested the fix but it should work. I'll see about doing a pull request soon but someone feel free to beat me to it.

Only one permission request at a time.

How can I deal this:

java.lang.IllegalStateException: Only one permission request at a time. Currently handling permission: [android.permission.WRITE_EXTERNAL_STORAGE]
11-20 00:15:17.036 27963-27963/? E/CustomActivityOnCrash: at com.karumi.dexter.DexterInstance.checkPermission(DexterInstance.java:51)
11-20 00:15:17.036 27963-27963/? E/CustomActivityOnCrash: at com.karumi.dexter.Dexter.checkPermission(Dexter.java:52)

Allow access to Snackbar instance within SnackbarOnDeniedPermissionListener

I have this use case where I'm using SnackbarOnDeniedPermissionListener to show a Snackbar notify the user of some denied permission with the Settings button allowing them to enable the required permission.

But I feel the need to perform some action when the Snackbar is dismissed (manually by opening the settings button or automatically when it times out). But I don't have access to the Snackbar instance so I can't invoke setCallback to handle the onDismissed callback. As such, I can't use SnackbarOnDeniedPermissionListener and had to roll out my own implementation of the same thing.

Maybe implement a withCallback method on the SnackbarOnDeniedPermissionListener?

What do you guys think? I could open a pull request for this...

Invisible activity is always created on background

When asking for permissions, the invisible activity is always created, this might cause some issues if Dexter is called from the onCreate or onResume methods of your own activity (as it will force another call to onCreate & onResume once the inner activity is dismissed). We should only create the inner Activity iff ActivityCompat.requestPermissions is going to be called.

For calling checkSelfPermission we should use the provided application context.

I have a suggestion

Android 6.0 version of the device can run normally, but run on earlier versions of the equipment, procedure will collapse.

if (Build.VERSION.SDK_INT >= 23) {  
    // Marshmallow+  
} else {  
    // Pre-Marshmallow  
}  

But this will cause the program to become bloated.A better solution is to use the Support Library v4 repository method to replace the original method, it will save for different versions of the device respectively provide code:

// Modify Activity.checkSelfPermission()
ContextCompat.checkSelfPermission()  

The code is more concise

Question about continue permission request for rationale

If one handles onPermissionRationaleShouldBeShown to show a rationale to the user explaining why we need that particular permission - for instance, with an AlertDialog - and call token.continuePermissionRequest() to show the request permission dialog again, this leads into two dialogs.

Is there anyway to avoid this? I mean, show a rationale within the request permission dialog?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.