Skip to content

Commit

Permalink
fix: Update to use advanceAndApply
Browse files Browse the repository at this point in the history
State machines should use `advanceAndApply` after Option-C merged, rather than just `advance`. Without this, users were seeing a bug where the first frame of an animation in a state machine was visible due to not settling nested artboards' states during initialization.

Additionally, because `advanceAndApply` advances the artboard, it is no longer necessary to advance the artboard separately.

Diffs=
4107e94902 fix: Update to use advanceAndApply (#8725)

Co-authored-by: Erik <[email protected]>
  • Loading branch information
ErikUggeldahl and ErikUggeldahl committed Dec 12, 2024
1 parent 4c33eca commit 86cf8af
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 132 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0cdfd3bf3286dc9314f31a6ae3f4449eec516ab3
4107e94902aa6edefde0436b923b06dad4120bea
1 change: 1 addition & 0 deletions kotlin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ dependencies {

androidTestImplementation "androidx.test.ext:junit-ktx:1.2.1"
androidTestImplementation "androidx.test:runner:1.6.2"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test-junit:2.1.0"
}

apply from: "${rootProject.projectDir}/scripts/publish-module.gradle"
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ package app.rive.runtime.kotlin.core
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import app.rive.runtime.kotlin.test.R
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertIs

@RunWith(AndroidJUnit4::class)
class RiveEventTest {
private val testUtils = TestUtils()
private val appContext = testUtils.context
private lateinit var view: TestUtils.MockRiveAnimationView


@Before
fun init() {
view = TestUtils.MockRiveAnimationView(appContext)
Expand All @@ -25,28 +25,26 @@ class RiveEventTest {
UiThreadStatement.runOnUiThread {
val observer = TestUtils.EventObserver()
view.setRiveResource(R.raw.events_test, stateMachineName = "State Machine 1")
view.addEventListener(observer);
view.play()
view.addEventListener(observer)
view.fireState("State Machine 1", "FireGeneralEvent")
// This advance processes the fire but because we internally grab the latest events
// before advancing the artboard, we don't catch "this frame's" events until the next
// advance, which is why we advance twice here.
view.artboardRenderer?.advance(0.016f);
view.artboardRenderer?.advance(0.016f)

// No events yet.
Assert.assertEquals(0, observer.events.size)
view.artboardRenderer?.advance(0.016f);
view.artboardRenderer?.advance(0.016f);
assertEquals(0, observer.events.size)

// Second advance reports the event triggered by fireState.
Assert.assertEquals(1, observer.events.size)
val event = observer.events[0];
Assert.assertTrue(event is RiveGeneralEvent)
Assert.assertEquals("SomeGeneralEvent", event.name)
Assert.assertEquals(EventType.GeneralEvent, event.type)
view.artboardRenderer?.advance(0.016f)
assertEquals(1, observer.events.size)
val event = observer.events[0]
assertIs<RiveGeneralEvent>(event)
assertEquals("SomeGeneralEvent", event.name)
assertEquals(EventType.GeneralEvent, event.type)
val expectedProperties =
hashMapOf("SomeNumber" to 11.0f, "SomeString" to "Something", "SomeBoolean" to true)
Assert.assertEquals(expectedProperties, event.properties)
view.artboardRenderer?.advance(0.016f);

assertEquals(expectedProperties, event.properties)
}
}

Expand All @@ -55,21 +53,21 @@ class RiveEventTest {
UiThreadStatement.runOnUiThread {
val observer = TestUtils.EventObserver()
view.setRiveResource(R.raw.events_test, stateMachineName = "State Machine 1")
view.addEventListener(observer);
view.addEventListener(observer)
view.play()
view.fireState("State Machine 1", "FireOpenUrlEvent")
view.artboardRenderer?.advance(0.016f);
view.artboardRenderer?.advance(0.016f)
// Events caught on second advance.
view.artboardRenderer?.advance(0.016f);
Assert.assertEquals(1, observer.events.size)
val event = observer.events[0];
Assert.assertTrue(event is RiveOpenURLEvent)
Assert.assertEquals("SomeOpenUrlEvent", event.name)
Assert.assertEquals(EventType.OpenURLEvent, event.type)
Assert.assertEquals("https://rive.app", (event as RiveOpenURLEvent).url)
Assert.assertEquals("_parent", (event).target)
val expectedProperties = hashMapOf<String, Any>();
Assert.assertEquals(expectedProperties, event.properties)
view.artboardRenderer?.advance(0.016f)
assertEquals(1, observer.events.size)
val event = observer.events[0]
assertIs<RiveOpenURLEvent>(event)
assertEquals("SomeOpenUrlEvent", event.name)
assertEquals(EventType.OpenURLEvent, event.type)
assertEquals("https://rive.app", (event as RiveOpenURLEvent).url)
assertEquals("_parent", (event).target)
val expectedProperties = hashMapOf<String, Any>()
assertEquals(expectedProperties, event.properties)
}
}

Expand All @@ -78,12 +76,12 @@ class RiveEventTest {
UiThreadStatement.runOnUiThread {
val observer = TestUtils.EventObserver()
view.setRiveResource(R.raw.events_test, stateMachineName = "State Machine 1")
view.addEventListener(observer);
view.addEventListener(observer)
view.play()
view.fireState("State Machine 1", "FireBothEvents")
view.artboardRenderer?.advance(0.016f);
view.artboardRenderer?.advance(0.016f);
Assert.assertEquals(2, observer.events.size)
view.artboardRenderer?.advance(0.016f)
view.artboardRenderer?.advance(0.016f)
assertEquals(2, observer.events.size)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ package app.rive.runtime.kotlin.core
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import app.rive.runtime.kotlin.RiveAnimationView
import app.rive.runtime.kotlin.core.errors.RiveException
import app.rive.runtime.kotlin.core.errors.TextValueRunException
import app.rive.runtime.kotlin.test.R
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertFailsWith


@RunWith(AndroidJUnit4::class)
Expand All @@ -27,7 +26,7 @@ class RiveTextValueRunTest {
file = File(
appContext.resources.openRawResource(R.raw.hello_world_text).readBytes()
)
nestePathFile= File(
nestePathFile = File(
appContext.resources.openRawResource(R.raw.runtime_nested_text_runs).readBytes()
)

Expand All @@ -36,7 +35,7 @@ class RiveTextValueRunTest {

@Test
fun read_and_update_text_run() {
var artboard = file.firstArtboard;
var artboard = file.firstArtboard
assertEquals(artboard.dependencies.count(), 0)

val textRun = artboard.textRun("name")
Expand All @@ -54,12 +53,12 @@ class RiveTextValueRunTest {

// Setting through the helper method
updateValue = "new value"
artboard.setTextRunValue("name",updateValue)
artboard.setTextRunValue("name", updateValue)
assertEquals(updateValue, textRun.text)
assertEquals(updateValue, artboard.getTextRunValue("name"))

// Only accessing .textRun should add to the dependencies.
assertEquals(artboard.dependencies.count(), 1)
assertEquals(1, artboard.dependencies.count())
}

@Test(expected = TextValueRunException::class)
Expand All @@ -69,22 +68,64 @@ class RiveTextValueRunTest {

@Test
fun read_and_update_text_run_at_path() {
val artboard = nestePathFile.firstArtboard;
assertEquals(artboard.dependencies.count(), 0)

nestedTextRunHelper(artboard, "ArtboardBRun", "ArtboardB-1","Artboard B Run", "ArtboardB-1" )
nestedTextRunHelper(artboard, "ArtboardBRun", "ArtboardB-2","Artboard B Run", "ArtboardB-2" )
nestedTextRunHelper(artboard, "ArtboardCRun", "ArtboardB-1/ArtboardC-1","Artboard C Run", "ArtboardB-1/C-1" )
nestedTextRunHelper(artboard, "ArtboardCRun", "ArtboardB-1/ArtboardC-2","Artboard C Run", "ArtboardB-1/C-2" )
nestedTextRunHelper(artboard, "ArtboardCRun", "ArtboardB-2/ArtboardC-1","Artboard C Run", "ArtboardB-2/C-1" )
nestedTextRunHelper(artboard, "ArtboardCRun", "ArtboardB-2/ArtboardC-2","Artboard C Run", "ArtboardB-2/C-2" )
val artboard = nestePathFile.firstArtboard
assertEquals(0, artboard.dependencies.count())

nestedTextRunHelper(
artboard,
"ArtboardBRun",
"ArtboardB-1",
"Artboard B Run",
"ArtboardB-1"
)
nestedTextRunHelper(
artboard,
"ArtboardBRun",
"ArtboardB-2",
"Artboard B Run",
"ArtboardB-2"
)
nestedTextRunHelper(
artboard,
"ArtboardCRun",
"ArtboardB-1/ArtboardC-1",
"Artboard C Run",
"ArtboardB-1/C-1"
)
nestedTextRunHelper(
artboard,
"ArtboardCRun",
"ArtboardB-1/ArtboardC-2",
"Artboard C Run",
"ArtboardB-1/C-2"
)
nestedTextRunHelper(
artboard,
"ArtboardCRun",
"ArtboardB-2/ArtboardC-1",
"Artboard C Run",
"ArtboardB-2/C-1"
)
nestedTextRunHelper(
artboard,
"ArtboardCRun",
"ArtboardB-2/ArtboardC-2",
"Artboard C Run",
"ArtboardB-2/C-2"
)

// Only accessing the textRun directly should increase the dependency.
// Calling getTextRunValue and setTextRunValue should not.
assertEquals(artboard.dependencies.count(), 6)
assertEquals(6, artboard.dependencies.count())
}

private fun nestedTextRunHelper(artboard: Artboard, name: String, path: String, originalValue: String, updatedValue: String) {
private fun nestedTextRunHelper(
artboard: Artboard,
name: String,
path: String,
originalValue: String,
updatedValue: String
) {
// Get the text value run. This should increase the dependency count
val textRun = artboard.textRun(name, path = path)

Expand All @@ -107,74 +148,63 @@ class RiveTextValueRunTest {
fun viewSetGetTextRun() {
UiThreadStatement.runOnUiThread {
mockView.setRiveResource(R.raw.hello_world_text, autoplay = false)
mockView.play(listOf("State Machine 1"), areStateMachines = true)
assertEquals(true, mockView.isPlaying)
mockView.play("State Machine 1", isStateMachine = true)
assertTrue(mockView.isPlaying)

assertEquals(mockView.controller.activeArtboard?.dependencies?.count(), 1)
assertEquals(1, mockView.controller.activeArtboard?.dependencies?.count())
val textValue = mockView.getTextRunValue("name")
assertEquals(textValue, "world")
assertEquals("world", textValue)

var newValue = "New Value";
val newValue = "New Value"
mockView.setTextRunValue("name", newValue)
val textValueUpdated = mockView.getTextRunValue("name")
assertEquals(textValueUpdated, newValue)
assertEquals(newValue, textValueUpdated)

assertEquals(mockView.controller.activeArtboard?.dependencies?.count(), 1)
assertEquals(1, mockView.controller.activeArtboard?.dependencies?.count())

// Test for throwing an error when giving a wrong text run name
try {
val exception1 = assertFailsWith<TextValueRunException> {
mockView.setTextRunValue("non_existent_text_run", "Some Value")
fail("Expected an exception to be thrown")
} catch (e: Exception) {
assertTrue(e is RiveException)
assertTrue(e.message?.contains("No Rive TextValueRun found") == true)
}
assertTrue(exception1.message?.contains("No Rive TextValueRun found") ?: false)

// Test for throwing an error when giving a wrong text run name for a nested artboard
try {
mockView.setTextRunValue("non_existent_text_run", "Some Value", "ArtboardB-1")
fail("Expected an exception to be thrown")
} catch (e: Exception) {
assertTrue(e is RiveException)
assertTrue(e.message?.contains("No Rive TextValueRun found") == true)
val exception2 = assertFailsWith<TextValueRunException> {
mockView.setTextRunValue("name", "Some Value", "non_existent_path")
}
assertTrue(exception2.message?.contains("No Rive TextValueRun found") ?: false)
}
}

@Test
fun viewSetGetNestedTextRun() {
UiThreadStatement.runOnUiThread {
mockView.setRiveResource(R.raw.runtime_nested_text_runs, autoplay = false)
mockView.play(listOf("State Machine 1"), areStateMachines = true)
mockView.play("State Machine 1", isStateMachine = true)
assertEquals(true, mockView.isPlaying)

assertEquals(mockView.controller.activeArtboard?.dependencies?.count(), 1)
assertEquals(1, mockView.controller.activeArtboard?.dependencies?.count())
val textValue = mockView.getTextRunValue("ArtboardBRun", "ArtboardB-1")
assertEquals(textValue, "Artboard B Run")
assertEquals("Artboard B Run", textValue)

var newValue = "New Value";
mockView.setTextRunValue("ArtboardBRun", newValue, "ArtboardB-1" )
val newValue = "New Value"
mockView.setTextRunValue("ArtboardBRun", newValue, "ArtboardB-1")
val textValueUpdated = mockView.getTextRunValue("ArtboardBRun", "ArtboardB-1")
assertEquals(textValueUpdated, newValue)
assertEquals(newValue, textValueUpdated)

assertEquals(mockView.controller.activeArtboard?.dependencies?.count(), 1)

// Test for throwing an error when giving a wrong text run name for a nested artboard
try {
val exception1 = assertFailsWith<TextValueRunException> {
mockView.setTextRunValue("non_existent_text_run", "Some Value", "ArtboardB-1")
fail("Expected an exception to be thrown")
} catch (e: Exception) {
assertTrue(e is RiveException)
assertTrue(e.message?.contains("No Rive TextValueRun found") == true)
}
assertTrue(exception1.message?.contains("No Rive TextValueRun found") ?: false)

// Test for throwing an error when giving a wrong path for a nested artboard
try {
val exception2 = assertFailsWith<TextValueRunException> {
mockView.setTextRunValue("ArtboardBRun", "Some Value", "non_existent_path")
fail("Expected an exception to be thrown")
} catch (e: Exception) {
assertTrue(e is RiveException)
assertTrue(e.message?.contains("No Rive TextValueRun found") == true)
}
assertTrue(exception2.message?.contains("No Rive TextValueRun found") ?: false)
}
}
}
}
Loading

0 comments on commit 86cf8af

Please sign in to comment.