Paul Robertson's words, punctuated

Thoughts on development, user-centered design, code, etc. by Paul Robertson

Useful Flash Builder “Burrito” keyboard shortcuts

Last night I was working through “Uncle” Bob Martin’s ”Bowling Game Kata” (thanks to John Lindquist for the link). I found myself going to my mouse a lot, doing things like switching between class and test case files and right-clicking to run tests. After slowing down too many times, I dug in and found a few keyboard shortcuts in Flash Builder “Burrito” that I think are very useful for a TDD workflow (or even for general development):

  • Ctrl+F6: Switch between editor tabs
  • Ctrl+F7: Switch between views (i.e. “panels”)
  • Alt+Shift+A F: Run all unit tests for the current project. (Note that pressing Alt+Shift+A actually pops up a little menu in the bottom right corner showing the command, so in practice you can just use Alt+Shift+A <Enter>
  • Alt+Shift+E F: Run unit tests for the active class. (In practice, you can just hit Alt+Shift+E <Enter>

I don’t actually think any of these are new in “Burrito” – I think they’re probably in Flash Builder 4 also.

If you’re interested in speeding up your development and reducing your mouse dependency, Holly Schinsky wrote an article on the ADC with several other (even more) useful keyboard shortcuts:

Flash Builder 4 shortcuts and debugging tips

FlashBuilder Burrito templates for Robotlegs and more

Update: thanks to Ondina’s suggestion, I’ve posted all these templates (individually and in groups) to a GitHub repository. Feel free to take them, change them, add your favorites, etc.

I’m really excited about the Flash Builder “Burrito” release that was added to Adobe Labs today. Even ignoring the new mobile publishing and Hero SDK features, I think the developer productivity features that have been added to Burrito make it the best Flash Builder (or Flex Builder) release since, well, ever.

One of my favorite features is the new templates (snippets) that you can use. To add a template, just start typing the name of the template and hit Ctrl/Cmd+Space. (If you see too many hints, hit Ctrl+Space again to just get template code hints.) Your cursor will automatically be placed in the first template field. Once you type that field, you can hit TAB to jump to the next template field.

However, the only way you can get benefit out of the templates is if you have templates to use! If you’ve used FDT, you may already have a bunch that you use. I found that for the most part FDT templates can be used in Flash Builder “Burrito” but there are a few minor differences in syntax for some of the template variables.

Anyway, to save you some trouble and give you a chance to try out templates in Flash Builder “Burrito,” I thought I’d share the templates that I use the most. To load them up, just open up preferences, then choose Flash Builder > Editors > Code Templates > ActionScript. Click the “import” button below the list of templates, and choose the XML file to import. Here are the templates I use the most, modified to work with Burrito:

  • Robotlegs templates - There are quite a few here, including “command body,” “map command,” “map context listener,” “map listener,” “map view listener,” “mediator body,” “post construct,” “unmap command,” and “unmap listener”
  • “Property” template (a complete “property” including getter, setter, and “backer” variable)
  • AS3-Signals template (I only use this one so far, which is for creating a Signal property on a class)
  • General ActionScript templates - by default the templates that come with Burrito have the opening curly brace on the same line rather than the next line. Naturally I had to fix that! This set of templates modifies the built in ones (plus I think it has a couple more that I added, but I can’t remember for sure).
  • All templates - If you really just want to trust me completely, here’s an export of all my templates in my current set in Burrito

Hopefully these are helpful for you to use and adapt. If you have any questions about them or your own templates to recommend, feel free to share them.

Robotlegs hands-on training slides and examples

Two days ago I presented a hands-on training session at 360|Flex Washington DC covering an introduction to Robotlegs and some of the common gotchas and misconceptions for people who are just getting started with Robotlegs.

As I always do, I wanted to make sure and post my slides and code examples:

In this case, my work was not all mine. Joel Hooks has been giving Robotlegs presentations for a while now and graciously shares his materials under an open license, so I definitely want to continue that. Consequently, you can find the source for my slides here:

Robotlegs hands-on training slides on Github

The two example applications I used are also available:

Robotlegs training at 360|Flex DC

I’m presenting at the upcoming 360|Flex conference in Washington D.C., Sept. 19. (Yes, that’s in less than 3 weeks!) This time I’m very excited to be doing a training session on Robotlegs as part of the pre-conference training.

Robotlegs at 360|Flex

If you’ve got some ActionScript and Flex experience and you want to learn to use Robotlegs to build more maintainable apps, or if you’ve wanted to get started with Robotlegs but haven’t been able to get your head around it quite yet, then this session is definitely for you.

Since it’s a training session, I’m going to talk a bit about the concepts and theory of Robotlegs, but mostly we’re going to be walking through building an app using the Robotlegs MVCS implementation – the “standard” Robotlegs implementation. We’ll work through several examples of common app “tasks.” We’ll use several of the common “variations” of MVCS that are used in various sample applications, blogs, and the real world, and talk about the benefits and downsides of them. I’ve got a big list of common new-user misconceptions and problems that I’m working in to the presentation. Finally, time permitting, I’m hoping to spend some time covering how to use Signals in combination with Robotlegs, since I know that’s a combination that many people are interested in.

So, if you haven’t registered for 360|Flex yet, there’s no better time than the present. It’s always a great show, and many of my best experiences with the Flex/ActionScript/AIR developer community are linked to a 360|Flex conference. So, what are you waiting for – go sign up right now!

In case you’re curious about my experience with Robotlegs, here’s a little background on my history with the framework. I started following Robotlegs closely last fall, and started building my first apps with it in February. In March I was very fortunate to be able to collaborate with Joel Hooks as he prepared his Robotlegs training session for the previous 360|Flex – I learned a ton from that experience. Since then, I’ve been working full time developing apps using Robotlegs, so I’ve gotten lots of real-world experience with the framework. In addition, I’ve been following the Robotlegs knowledge base/question forum pretty much since it started, (lately I’ve even been contributing answers =) so I’ve gotten a good view of the common questions and problems that people run into while getting started and working with Robotlegs.

360|Flex airport ride sharing

I haven’t mentioned this yet, but I’m speaking at the upcoming 360|Flex (Washington D.C.) conference in late September. When I was booking my travel for the conference, I realized that the hotel location is pretty far from all three major airports in the D.C. area. The last time I went to Washington D.C. I used Super Shuttle to go between the airport and my hotel and I had a decent experience with them. I’m definitely going to be using Super Shuttle, but I thought others might be interested in saving some money by coordinating shared rides (on Super Shuttle or with people who are renting cars or live in the DC area).

Both surveys are set to close on August 31, but please fill them out as soon as you can so we can get the best prices.

In any case I won’t book anything without your explicit approval, so there’s no commitment for filling out the survey.

Finally, please tweet about this or share this with others who are going to the conference – it will be much more successful if more people respond.

Thanks in advance!

Simple ActionScript 3 animation examples

Last week I spent a little time teaching some of the newer developers in our office about scripted animation in ActionScript 3. I put together a few simple demos for them, and I thought I might as well share them with the world.

Note that these are basic demos so if you know pretty much anything about animation in ActionScript, these may not teach you anything.

All these scripts do pretty much the same thing. They draw a square on the stage. When you click on it, it moves horizontally from x=100 to x=400. Each of these is a class that you can run as the main class of an AS3 application.

1. The wrong way

The wrong way, of course, is to use a for loop:

package
{
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.display.Sprite;
    public class BasicAnimation extends Sprite
    {
        // ------- Constructor -------
        
        public function BasicAnimation()
        {
            _createChildren();
        }
        
        
        // ------- Child display objects -------
        
        private var _rect:Sprite;
        
        
        // ------- Private constants -------
        
        private const START:int = 100;
        private const END:int = 400;

        
        // ------- Private methods -------
        
        private function _createChildren():void 
        {
            _rect = new Sprite();
            _rect.graphics.beginFill(0xff0000, 1);
            _rect.graphics.drawRect(0, 0, 100, 100);
            _rect.graphics.endFill();
            _rect.x = START;
            addChild(_rect);
            _rect.addEventListener(MouseEvent.CLICK, _rect_click);
        }
        
        
        // ------- Event handling -------
        
        private function _rect_click(event:MouseEvent):void 
        {
            // wrong way:
            _rect.x = START;
            while (_rect.x < END)
            {
                _rect.x++;
            }
        }
    }
}

The right way (most basic)

Of course, this one runs very slow since it’s moving at a rate of 1 pixel per frame.

package
{
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.display.Sprite;
    public class BasicAnimation extends Sprite
    {
        // ------- Constructor -------
        
        public function BasicAnimation()
        {
            _createChildren();
        }
        
        
        // ------- Child display objects -------
        
        private var _rect:Sprite;
        
        
        // ------- Private constants -------
        
        private const START:int = 100;
        private const END:int = 400;

        
        // ------- Private methods -------
        
        private function _createChildren():void 
        {
            _rect = new Sprite();
            _rect.graphics.beginFill(0xff0000, 1);
            _rect.graphics.drawRect(0, 0, 100, 100);
            _rect.graphics.endFill();
            _rect.x = START;
            addChild(_rect);
            _rect.addEventListener(MouseEvent.CLICK, _rect_click);
        }
        
        
        // ------- Event handling -------
        
        private function _rect_click(event:MouseEvent):void 
        {
            // right way (very basic):
            addEventListener(Event.ENTER_FRAME, _enterFrameBasic);
        }
        
        
        private function _enterFrameBasic(event:Event):void 
        {
            if (_rect.x < END)
            {
                _rect.x++;
            }
            else
            {
                removeEventListener(Event.ENTER_FRAME, _enterFrameBasic);
            }
        }
    }
}

Adding velocity as a variable

The solution to the slowness is to add a velocity variable. The value (in units of pixels per frame) will determine how quickly the animation moves. Basically you just try different values until you’re happy with something. Like the previous example, this is a purely linear animation – the velocity stays constant.

package
{
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.display.Sprite;
    public class BasicAnimation extends Sprite
    {
        // ------- Constructor -------
        
        public function BasicAnimation()
        {
            _createChildren();
        }
        
        
        // ------- Child display objects -------
        
        private var _rect:Sprite;
        
        
        // ------- Private constants -------
        
        private const START:int = 100;
        private const END:int = 400;

        
        // ------- Private methods -------
        
        private function _createChildren():void 
        {
            _rect = new Sprite();
            _rect.graphics.beginFill(0xff0000, 1);
            _rect.graphics.drawRect(0, 0, 100, 100);
            _rect.graphics.endFill();
            _rect.x = START;
            addChild(_rect);
            _rect.addEventListener(MouseEvent.CLICK, _rect_click);
        }
        
        
        // ------- Event handling -------
        
        private function _rect_click(event:MouseEvent):void 
        {
            // right way (very basic):
            addEventListener(Event.ENTER_FRAME, _enterFrameBasic);
        }
        
        
        private function _enterFrameBasic(event:Event):void 
        {
            if (_rect.x < END)
            {
                var velocity:int = 5;
                _rect.x += velocity;
            }
            else
            {
                removeEventListener(Event.ENTER_FRAME, _enterFrameBasic);
            }
        }
    }
}

Adding acceleration

Once you’ve separated out velocity, it becomes easy to make the animation non-linear by adding an acceleration value that causes the velocity to change over time. We’re just stepping back one level of complexity – the acceleration is now linear instead of the velocity.

package
{
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.display.Sprite;
    public class BasicAnimation extends Sprite
    {
        // ------- Constructor -------
        
        public function BasicAnimation()
        {
            _createChildren();
        }
        
        
        // ------- Child display objects -------
        
        private var _rect:Sprite;
        
        
        // ------- Private constants -------
        
        private const START:int = 100;
        private const END:int = 400;

        
        // ------- Private methods -------
        
        private function _createChildren():void 
        {
            _rect = new Sprite();
            _rect.graphics.beginFill(0xff0000, 1);
            _rect.graphics.drawRect(0, 0, 100, 100);
            _rect.graphics.endFill();
            _rect.x = START;
            addChild(_rect);
            _rect.addEventListener(MouseEvent.CLICK, _rect_click);
        }
        
        
        // ------- Event handling -------
        
        private function _rect_click(event:MouseEvent):void 
        {
            // right way (very basic):
            addEventListener(Event.ENTER_FRAME, _enterFrameBasic);
        }
        
        
        private var _velocity:int = 0;
        
        private function _enterFrameBasic(event:Event):void 
        {
            if (_rect.x < END)
            {
                var accel:int = 3;
                _velocity += accel;
                _rect.x += _velocity;
            }
            else
            {
                removeEventListener(Event.ENTER_FRAME, _enterFrameBasic);
            }
        }
    }
}

Interpolating values over time

This next example doesn’t build on the previous ones but instead takes a completely separate approach to creating animation. This animation is time-driven rather than frame-driven. Instead of adding a particular number of pixels to the x value each frame, it calculates how much time has elapsed since the start and what the x value should be at that moment in time, then sets the x property to that value. A consequence of this approach is that regardless of the frame rate of the SWF, the animation will always last the same amount of time.

package  
{
    import flash.utils.getTimer;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.display.Sprite;
    public class AnimationIntermediate extends Sprite 
    {
        // ------- Constructor -------
        
        public function AnimationIntermediate()
        {
            _createChildren();
        }
        
        
        // ------- Child display objects -------
        
        private var _rect:Sprite;
        
        
        // ------- Private constants -------
        
        private const START:int = 100;
        private const END:int = 400;
        private const TOTAL_TIME:int = 5000;

        
        // ------- Private methods -------
        
        private function _createChildren():void 
        {
            _rect = new Sprite();
            _rect.graphics.beginFill(0xff0000, 1);
            _rect.graphics.drawRect(0, 0, 100, 100);
            _rect.graphics.endFill();
            _rect.x = START;
            addChild(_rect);
            _rect.addEventListener(MouseEvent.CLICK, _rect_click);
        }
        
        
        // ------- Event handling -------
        
        private var _startTime:int = 0;
        
        private function _rect_click(event:MouseEvent):void 
        {
            // right way (intermediate):
            _startTime = getTimer();
            addEventListener(Event.ENTER_FRAME, _enterFrameIntermediate);
        }
        
        
        private function _enterFrameIntermediate(event:Event):void 
        {
            var elapsedTime:int = getTimer() - _startTime;
            var fractionComplete:Number = elapsedTime / TOTAL_TIME;
            
            if (fractionComplete < 1)
            {
                var totalDistance:int = END - START;
                var offset:int = Math.round(fractionComplete * totalDistance);
                _rect.x = START + offset;
            }
            else
            {
                _rect.x = END;
                removeEventListener(Event.ENTER_FRAME, _enterFrameIntermediate);
            }
        }
    }
}

The main point of this example is to demonstrate the idea that you can plug in an algorithm that calculates position given time, and by varying the algorithm you can vary the animation. This is a direct lead-in to the idea of using tweening functions to vary an animation, as demonstrated in the next example.

Using an animation library

This example shows the use of a third-party animation library (in this case Grant Skinner’s GTween library). The commented-out lines demonstrate how a library makes it easy to synchronize animation and vary the animation. Note that this is only meant as a very basic demonstration of the concept of using a library for animation – GTween and the many other animation libraries that are out there all provide many additional features.

package  
{
    import com.gskinner.motion.easing.Bounce;
    import com.gskinner.motion.easing.Sine;
    import com.gskinner.motion.GTween;
    import flash.events.MouseEvent;
    import flash.display.Sprite;
    public class AnimationTween extends Sprite 
    {
        // ------- Constructor -------
        
        public function AnimationTween()
        {
            _createChildren();
        }
        
        
        // ------- Child display objects -------
        
        private var _rect:Sprite;
        
        
        // ------- Private constants -------
        
        private const START:int = 100;
        private const END:int = 400;
        private const TOTAL_TIME:int = 5;
        
        
        // ------- Private methods -------
        
        private function _createChildren():void 
        {
            _rect = new Sprite();
            _rect.graphics.beginFill(0xff0000, 1);
            _rect.graphics.drawRect(0, 0, 100, 100);
            _rect.graphics.endFill();
            _rect.x = START;
            addChild(_rect);
            _rect.addEventListener(MouseEvent.CLICK, _rect_click);
        }
        
        
        // ------- Event handling -------
        
        private function _rect_click(event:MouseEvent):void 
        {
            var tween:GTween = new GTween(_rect, TOTAL_TIME);
            tween.setValue("x", END);
//            tween.setValue("alpha", .5);
//            tween.ease = Sine.easeInOut;
//            tween.ease = Bounce.easeOut;
        }
    }
}

Changes

Unfortunately I can’t give this topic the time it deserves right now, but for those of you who are interested I wanted to let you know of some “significant life changes” that are happening to me.

Summary

New job. New city. New house. Moving tomorrow. Offline for a week or two. Don’t say anything important while I’m gone. (Sorry FATC, especially Reflex: no breaking announcements or innovation allowed. =)

1. Job

I am now no longer working for Adobe. I have started a new job with Dedo Interactive, Inc., as a Flash/Flex/ActionScript/AIR developer and also as an active collaborator with the UX design group. As Danny and Jonathan have already described, this is an exciting opportunity with a group that’s pushing the boundaries of hardware and software solutions. It’s also exciting for me to be a “regular developer” like everyone I hang out with and pretend to fit in with at conferences. Of course that’s where my roots are – in being a Macromedia/Adobe customer first before being an Adobe employee – so I’m just going back to the place that I’ve tried my best not to leave anyway.

2. Location

Since Dedo is located in Plano (N. Dallas) Texas, naturally it seems that I need to be located there too. Danny has been really great about letting me start as a remote employee. Nevertheless, I am typing this while mostly surrounded by cardboard boxes, as well as a few too many things that I should have already packed into cardboard boxes. In approximately 32 hours a moving truck will pull up to my house and load everything up. Then I’ll pack my family into a minivan and drive for many hours and days until we get to our new house. (Just signed the mortgage papers today – ack!)

3. Family

Fortunately, my family has agreed to accompany me on this crazy journey. I feel sad for my three young children. They’re obviously not happy to leave their friends and school and the life that they’ve known for the last three years. (My youngest doesn’t remember anything before California, and my middle child barely remembers anything else, in spite of the fact that they were both born in Indiana.) I’m grateful to have a wonderful wife and special children who make the hard times worth it, and who make the good times good.

4. Blog, Twitter, open source projects, conferences, etc.

Frankly, I have no plans to change what I’ve been doing with any of those. I’ve never been the most regular blogger or twitterer, and my projects don’t get updated as often as I’d like. That probably isn’t going to change with a new job and a new city. But the vast majority of my work on them has been on my own time anyway, so the fact that I’m no longer an Adobe employee shouldn’t make that situation any worse. In fact, the owners of Dedo said that they’re pleased with my community involvement, and want it to continue. I’m definitely still using AIR, ActionScript, and SQLite in my work, so I keep finding new things and ways to improve my projects.

5. Blah blah sentimental stuff

Adobe is a great employer, and a great company. In spite of all the negativity and mud-slinging that’s happening right now, I can say that I never felt bad or guilty about working for Adobe. On the contrary, I am proud to have worked for Adobe. I feel like Adobe is a good, ethical company that tries to make business decisions that lead to mutual benefit for partners and customers. (Don’t bother sharing your experiences to the contrary. I’m sure I could tell you plenty too. In spite of that, this is my opinion of the company and employees overall.)

In addition to my great employer, living and working and associating with the community in the San Francisco bay area has been a tremendous experience, and I consider myself very fortunate for it. I’m definitely sorry to leave, especially because of the great opportunities (being near Adobe HQ), the great community (I’m looking at you SilvaFUG), and the great weather =)

To all of you who I’ve met and become friends with over the last 3 years, farewell, and I look forward to seeing you online and at conferences of the future. I only regret that I didn’t get to come to one last meeting and tell you goodbye in person.

Meanwhile, I’ve been amazed to learn how many talented and community-oriented developers call the Dallas-Fort Worth area home. I’ve already attended one users’ group meeting and plan to attend many more. I look forward to getting to know you better.

As I’ve been preparing to move, I’ve been trying to go through my many boxes that I haven’t touched since the last time I moved across the country. I was a little astounded to find so many of my college (and even a few high school) notes, papers, textbooks, etc. among my possessions. I’m getting older and more cynical now, plus we have to pay the movers by the pound, so I tried to get rid of as much as I could bear. I was amazed at how much I’ve forgotten from what I once knew. (Some of my academic papers were quite impressive to me – and it was astounding to find my 2nd year Hebrew notebook and later my calculus notes, and consider the fact that I wrote all the scribblings in there. =) I also couldn’t help but think over the many people I’ve met and known and worked with in Houston (where I grew up), Salt Lake City (where I did my undergraduate), Bloomington Indiana (grad school and my first “real” job), and of course most recently the bay area. It’s quite astounding to consider the path that life has led me on, and how I’ve ended up where I am today.

Data “paging” in AIR SQLite

I got an interesting question a few days ago that I thought I’d share:

My application has thousands of records in one particular table that I need to populate the display with. I was wondering if I can implement paging to speed up the retrieval of those records?

In fact he specifically wanted to know if it was possible to do data paging with the SQLRunner class in my air-sqlite library. The answer is yes it works, without even needing any changes to the library. See below for how to do that.

The easiest way to implement data paging (in other words, getting only a subset of a query’s results at a time) in a SELECT statement is to use the LIMIT..OFFSET clause in the SELECT statement.

In summary, you can put a LIMIT clause at the end of a SELECT statement and the result set will only include the specified number of rows:

SELECT * FROM myTable
LIMIT 3

Then to get the next 3 rows, you add an OFFSET value:

SELECT * FROM myTable
LIMIT 3 OFFSET 3

Fortunately, you can use statement parameters for the LIMIT and OFFSET values (although the docs don’t mention this – shame on me!):

SELECT * FROM myTable
LIMIT :limit OFFSET :offset

Then you just specify values for those parameters and you’re good to go.

I tested this using the SQLRunner class by adding a new set of unit tests and it worked just fine without any changes to the library. (Hooray for unit tests and FlexUnit support in Flash Builder 4 – they made it nice and easy to test this out since I already had the infrastructure in place.)

Here is the SQL statement I used for the unit test using LIMIT..OFFSET with parameters:

SELECT colString,
colInt
FROM main.testTable
LIMIT :limit OFFSET :offset

And here is the line of code that calls the statement (modified slightly for readability). test_result is the result handler function. The parameters object specifies that I want 7 result rows, starting with row number 3 (i.e. skipping 2):

_sqlRunner.execute(SQL, {limit:7, offset:2}, test_result);

As Peter points out in the comments, another approach to do something similar to data paging with AIR is to specify a number of rows to retrieve as the first argument to the SQLStatement.execute() method, then call the SQLStatement.next() method to retrieve additional rows from the same statement. This technique is described in the documentation so I won’t go into more detail about it here except to say that it does have a couple of drawbacks (mentioned in my comment below) that make it less suitable for data paging (but still very useful).

AIR SQLite library updates

A couple of people have reported a bug in my AIR SQLite utility library. I also recently used it to help build a Robotlegs demo app for the 360|Flex Robotlegs training, and in the process I discovered a missing feature I needed (namely, the ability to get back the SQLResult objects after running a batch of statements using SQLRunner.executeModify()).

Warning! To add the missing feature I had to introduce a non-backwards-compatible api change. Read the details in the project history.

So, the bug is fixed, the feature is added, the version number is incremented (0.1.1 beta), and the code and SWC are live on Github.

Download the version 0.1.1 SWC

Read about the changes

In conjunction with the release I also added a couple of new examples to the project page, including a “bare bones” code only example for quick starters, and links to an example of using the library in a Robotlegs application (the example app from the 360|Flex training).

Enjoy, and as always feel free to report problems as issues in Github or in the comments on the project page.

Slides, examples, and links: Developing iPhone apps with the Flash Platform

Flash logo pointing to iPhone with presentation title

Last week I gave a presentation at 360|Flex 2010 (San Jose) on “Developing iPhone apps with the Flash Platform.” As always, I wanted to make my slides, notes, reference links, and example code available to those who were there and those who couldn’t make it:

Download slides, links, and example code (3.1 MB .zip)

I’ve been traveling since the conference so I didn’t flesh out my notes as thoroughly as I have for past presentations. If you have a question about a slide, feel free to ask in the comments and I’ll try to explain it better. The 360|Flex folks recorded video of my presentation as well as a screen capture. I believe the plan is to make those recordings available to attendees as well as people who couldn’t make it (though they may charge a fee if you didn’t attend the conference).

Acknowledgments

This is in the slides, but I wanted to once again thank all the people who helped me prepare this presentation (some knowingly, others just by virtue of content they’ve shared):