Skip to content

Commit

Permalink
Merge pull request #8 from LikeTheSalad/release/2.1.0
Browse files Browse the repository at this point in the history
Release/2.1.0
  • Loading branch information
LikeTheSalad authored Feb 5, 2023
2 parents 199f3b2 + 46e726c commit 47b0510
Show file tree
Hide file tree
Showing 14 changed files with 154 additions and 68 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Change Log
==========

Version 2.1.0 *(05-02-2023)*
---

* Using androidx.startup to automatically initialize Aaper with its default config (thanks
@msasikanth)

Version 2.0.0 *(04-02-2023)*
---

Expand Down
105 changes: 65 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,6 @@ runtime permission requests.

### Default behavior example

```kotlin
// Aaper initialization
package my.app

class MyApplication {

override fun onCreate() {
super.onCreate()
Aaper.init()// This must be called only once, therefore the Application.onCreate method is a great place to do so.
}
}
```

```xml
<!--Your AndroidManifest.xml-->

Expand All @@ -51,11 +38,6 @@ class MyApplication {
<!--THIS IS VERY IMPORTANT!!-->
<uses-permission
android:name="android.permission.CAMERA" /> <!--Declare the permission in your manifest. Otherwise the runtime request won't work.-->

<!--You need to add your application class (shown above) to the manifest too as shown below-->
<application android:name="my.app.MyApplication">
<!--yada yada...-->
</application>
</manifest>
```

Expand Down Expand Up @@ -93,16 +75,13 @@ under `Changing the default behavior`.

How to use
---
As we could see above in the default behavior example, there are three things we need to do in order
As we could see above in the default behavior example, there are two things we need to do in order
to use Aaper into our Activities or Fragments:

- **Step one:** Make sure that the permissions you'll request with Aaper **are defined in
your** `AndroidManifest.xml` **file too**. If you attempt to request a permission at runtime that
isn't in your manifest, the OS will silently ignore your request.
- **Step two:** Initialize Aaper, this can be done by calling `Aaper.init()` only once, therefore a
great place to do it is in your app's `Application.onCreate` method, as shown in the example
above.
- **Step three:** Annotate an Activity or Fragment method with the `@EnsurePermissions` annotation
- **Step two:** Annotate an Activity or Fragment method with the `@EnsurePermissions` annotation
where you provide a list of permissions (that are also defined in your `AndroidManifest.xml`) that
such method needs in order to work properly. Alternatively, you can also pass an optional
parameter named `strategyName`, where you can specify the behavior of handling such permissions'
Expand Down Expand Up @@ -228,20 +207,37 @@ under `Advanced configuration`.
#### Registering it

In order to use our new `FinishActivityOnDeniedStrategy` request strategy, we must first register it
right after Aaper's initialization:
once, therefore a good place to do so would be in your app's `Application.onCreate` method:

```kotlin
package my.app

class MyApp : Application() {

override fun onCreate() {
super.onCreate()
Aaper.init()
val strategyProvider = Aaper.getRequestStrategyProvider() as DefaultRequestStrategyProvider
val strategyProvider = Aaper.getRequestStrategyProvider<DefaultRequestStrategyProvider>()
strategyProvider.register(FinishActivityOnDeniedStrategy())
}
}
```

**NOTE**: Make sure your application class is set in your `AndroidManifest.xml` file as shown below:

```xml
<!--Your AndroidManifest.xml-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<!--yada yada...-->

<!--You need to add your application class (shown above) to the manifest too (if you don't have it already) as shown below-->
<application android:name="my.app.MyApplication">
<!--yada yada...-->
</application>
</manifest>
```

We can register as many Strategies as we like, as long as they all have unique names. After
registering our new `RequestStrategy`, we can either:

Expand Down Expand Up @@ -365,7 +361,7 @@ In order to add the [Aaper plugin](https://plugins.gradle.org/plugin/com.likethe
your project, you just have to add the following line into your app's build.gradle `plugins` block:

```groovy
id 'com.likethesalad.aaper' version '2.0.0'
id 'com.likethesalad.aaper' version '2.1.0'
```

**Full app's build.gradle example:**
Expand All @@ -374,18 +370,13 @@ id 'com.likethesalad.aaper' version '2.0.0'
// Your app's build.gradle file
plugins {
id 'com.android.application'
id 'com.likethesalad.aaper' version '2.0.0'
id 'com.likethesalad.aaper' version '2.1.0'
}
```

Troubleshooting
---

### I get a NPE when calling my annotated method at runtime

Make sure you've called `Aaper.init()` within your Application class. If you don't initialize Aaper
it will throw a `java.lang.NullPointerException` when executed.

### The OS permission request dialog doesn't show up

Make sure that the permissions you've added to the `EnsurePermissions` annotation are ALSO added to
Expand All @@ -398,9 +389,9 @@ Advanced configuration
### Creating a custom RequestStrategyProvider

Aaper's behavior is all about its `RequestStrategy` objects, and the way Aaper can access to them,
is through an instance of `RequestStrategyProvider`. By default, when you initialize Aaper like
so: `Aaper.init()`, the `RequestStrategyProvider` that Aaper will use in that case, would
be `DefaultRequestStrategyProvider`.
is through an instance of `RequestStrategyProvider`. By default, the `RequestStrategyProvider` that
Aaper will use is `DefaultRequestStrategyProvider`, if you need to change it, take a look
at `Using your custom RequestStrategyProvider`.

The `DefaultRequestStrategyProvider` implementation contains a map of `RequestStrategy` instances to
which Aaper can access later on by providing the name of the Strategy it needs, such default
Expand Down Expand Up @@ -435,20 +426,54 @@ on the class and its methods: https://javadoc.io/doc/com.likethesalad.android/aa

#### Using your custom RequestStrategyProvider

After you've created your own `RequestStrategyProvider`, you can set it into Aaper's initialization
method like so:
After you've created your own `RequestStrategyProvider`, you need to disable Aaper's automatic
initialization in your `AndroidManifest.xml` file like so:

```xml

<application>
<provider android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup" android:exported="false"
tools:node="merge">
<meta-data android:name="com.likethesalad.android.aaper.AaperInitializer"
tools:node="remove" />
</provider>
</application>
```

> More info on disabling androidx startup initializers [here](https://developer.android.com/topic/libraries/app-startup#disable-individual).
Once you disabled the automatic initialization, you can manually call `Aaper.setUp` to pass your
custom `RequestStrategy`.

```kotlin
package my.app

class MyApp : Application() {

override fun onCreate() {
super.onCreate()
val myRequestStrategyProvider = MyRequestStrategyProvider()
Aaper.init(myRequestStrategyProvider)
Aaper.setUp(this, myRequestStrategyProvider)
}
}
```

**NOTE**: Make sure your application class is set in your `AndroidManifest.xml` file as shown below:

```xml
<!--Your AndroidManifest.xml-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<!--yada yada...-->

<!--You need to add your application class (shown above) to the manifest too (if you don't have it already) as shown below-->
<application android:name="my.app.MyApplication">
<!--yada yada...-->
</application>
</manifest>
```

And that's it, Aaper will now use your custom `RequestStrategyProvider` in order to get all of the
Strategies it needs.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ object PermissionManager {

private var strategyProviderSource: RequestStrategyProviderSource? = null
private val strategyProvider by lazy {
strategyProviderSource!!.getRequestStrategyProvider()
strategyProviderSource!!.getRequestStrategyProvider<RequestStrategyProvider>()
}
private var currentRequest: CurrentRequest? = null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import com.likethesalad.android.aaper.api.base.RequestStrategyProvider

interface RequestStrategyProviderSource {

fun getRequestStrategyProvider(): RequestStrategyProvider
fun <T : RequestStrategyProvider> getRequestStrategyProvider(): T
}
3 changes: 2 additions & 1 deletion aaper/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ android {

dependencies {
api project(":aaper-api")
api "androidx.startup:startup-runtime:1.1.1"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.core:core-ktx:1.3.1'
implementation "androidx.fragment:fragment-ktx:$fragment_version"
testImplementation "com.likethesalad.tools.testing:unit-testing:$testingUtilities_version"
testImplementation "org.robolectric:robolectric:$robolectric_version"
}
}
16 changes: 15 additions & 1 deletion aaper/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,2 +1,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.likethesalad.android.aaper" />
xmlns:tools="http://schemas.android.com/tools"
package="com.likethesalad.android.aaper">

<application>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.likethesalad.android.aaper.AaperInitializer"
android:value="androidx.startup" />
</provider>
</application>
</manifest>
28 changes: 19 additions & 9 deletions aaper/src/main/java/com/likethesalad/android/aaper/Aaper.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.likethesalad.android.aaper

import android.content.Context
import com.likethesalad.android.aaper.api.PermissionManager
import com.likethesalad.android.aaper.api.base.RequestStrategyProvider
import com.likethesalad.android.aaper.defaults.DefaultRequestStrategyProvider
import com.likethesalad.android.aaper.defaults.strategies.DefaultRequestStrategy
import com.likethesalad.android.aaper.errors.AaperInitializedAlreadyException
import com.likethesalad.android.aaper.internal.base.RequestStrategyProviderSource

Expand All @@ -24,30 +24,40 @@ object Aaper : RequestStrategyProviderSource {
* instance by a custom one, you can pass yours here. Otherwise, the default will be
* [DefaultRequestStrategyProvider].
*/
@Deprecated(message = "Deprecated after adding automatic initialization support on version 2.1.0, there's no longer need to manually call this method.")
@JvmOverloads
fun init(strategyProvider: RequestStrategyProvider = DefaultRequestStrategyProvider()) {
// No operation
}

/**
* Initializes Aaper
* This method is supposed to be called automatically so there's no need for it to be called,
* unless you wanted to override the default behavior, should it be the case, you'd need to
* disable its automatic initialization first. More info on the README.
*
* @param context - The app context
* @param strategyProvider - Will delegate permission requests to its strategies.
*/
fun setUp(context: Context, strategyProvider: RequestStrategyProvider) = synchronized(this) {
if (initialized) {
throw AaperInitializedAlreadyException()
}

initialized = true

this.strategyProvider = strategyProvider
PermissionManager.setStrategyProviderSource(this)

if (strategyProvider is DefaultRequestStrategyProvider) {
strategyProvider.register(DefaultRequestStrategy())
strategyProvider.setDefaultStrategyName(DefaultRequestStrategy.NAME)
}
initialized = true
}

/**
* Returns the instance of [RequestStrategyProvider] being used, which is the one set on
* the [init] function. If no custom instance was passed, then this function will return a
* [DefaultRequestStrategyProvider] one.
*/
override fun getRequestStrategyProvider(): RequestStrategyProvider {
return strategyProvider
@Suppress("UNCHECKED_CAST")
override fun <T : RequestStrategyProvider> getRequestStrategyProvider(): T {
return strategyProvider as T
}

fun resetForTest() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.likethesalad.android.aaper

import android.content.Context
import androidx.startup.Initializer
import com.likethesalad.android.aaper.defaults.DefaultRequestStrategyProvider
import com.likethesalad.android.aaper.defaults.strategies.DefaultRequestStrategy

class AaperInitializer : Initializer<Unit> {

override fun create(context: Context) {
val strategyProvider = DefaultRequestStrategyProvider()
strategyProvider.register(DefaultRequestStrategy())
strategyProvider.setDefaultStrategyName(DefaultRequestStrategy.NAME)

Aaper.setUp(context, strategyProvider)
}

override fun dependencies(): List<Class<out Initializer<*>>> {
return emptyList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.likethesalad.android.aaper.Aaper
import com.likethesalad.android.aaper.api.errors.AaperException

/**
* This exception is thrown whenever [Aaper.init] is called more than once.
* This exception is thrown whenever [Aaper.setUp] is called more than once.
*/
class AaperInitializedAlreadyException
: AaperException(
Expand Down
23 changes: 16 additions & 7 deletions aaper/src/test/java/com/likethesalad/android/aaper/AaperTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,20 @@ class AaperTest {

@Test
fun `Check initialization valid once only`() {
Aaper.init()
init()

try {
Aaper.init()
init()
fail("Should've gone into the catch block")
} catch (ignored: AaperInitializedAlreadyException) {
}
}

@Test
fun `Check default initialization strategy provider`() {
Aaper.init()
init()

Truth.assertThat(Aaper.getRequestStrategyProvider())
Truth.assertThat(Aaper.getRequestStrategyProvider<DefaultRequestStrategyProvider>())
.isInstanceOf(DefaultRequestStrategyProvider::class.java)
}

Expand All @@ -54,7 +54,7 @@ class AaperTest {
mockk<DefaultRequestStrategyProvider>(relaxUnitFun = true)
val strategyCaptor = slot<RequestStrategy<Any>>()

Aaper.init(defaultRequestStrategyProvider)
init(defaultRequestStrategyProvider)

verify {
defaultRequestStrategyProvider.register(capture(strategyCaptor))
Expand All @@ -70,9 +70,18 @@ class AaperTest {
fun `Return strategy provider set in the initialization`() {
val provider = mockk<RequestStrategyProvider>()

Aaper.init(provider)
init(provider)

Truth.assertThat(Aaper.getRequestStrategyProvider()).isEqualTo(provider)
Truth.assertThat(Aaper.getRequestStrategyProvider<RequestStrategyProvider>())
.isEqualTo(provider)
}

private fun init(provider: RequestStrategyProvider = DefaultRequestStrategyProvider()) {
if (provider is DefaultRequestStrategyProvider) {
provider.register(DefaultRequestStrategy())
provider.setDefaultStrategyName(DefaultRequestStrategy.NAME)
}
Aaper.setUp(mockk(), provider)
}

@Suppress("CAST_NEVER_SUCCEEDS")
Expand Down
Loading

0 comments on commit 47b0510

Please sign in to comment.